全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 10 分钟 ✍️ juanwangdev

插槽与作用域插槽底层实现

插槽本质是prop传递,普通插槽预渲染VNode,作用域插槽是返回VNode的函数,支持子组件向父组件传数据。

插槽VNode数据结构

JavaScript
// 父组件传递
<Child>
  <span>默认内容</span>
  <template v-slot:header>
    <h1>标题</h1>
  </template>
</Child>

// 子组件接收
{
  $slots: {
    default: [VNode],     // 预渲染的VNode数组
    header: [VNode]
  },
  $scopedSlots: {
    default: function(props) { ... },  // 返回VNode的函数
    header: function(props) { ... }
  }
}

普通插槽是VNode数组,作用域插槽是返回VNode的函数。

插槽编译过程

父组件编译

JavaScript
// 模板: <Child><span>{{ msg }}</span></Child>
// 编译后
createElement(Child, {}, {
  default: () => [createElement('span', [_v(_s(msg))])]
})

父组件将插槽内容编译为函数,存储在slots选项中。

子组件访问

JavaScript
// 子组件render函数
render(h) {
  return h('div', [
    this.$slots.default,           // 直接使用VNode
    this.$scopedSlots.footer?.({   // 执行函数获取VNode
      year: 2024
    })
  ])
}

子组件通过 $slots 获取VNode,通过 $scopedSlots 执行函数。

作用域插槽数据流

JavaScript
// 子组件
{
  props: ['items'],
  render(h) {
    return h('ul', [
      this.items.map((item, index) => 
        this.$scopedSlots.default({
          item,      // 子组件数据
          index
        })
      )
    ])
  }
}

// 父组件使用
<Child :items="list" v-slot="{ item, index }">
  <li>{{ item.name }} - {{ index }}</li>
</Child>

子组件将数据作为参数传入插槽函数,父组件通过解构接收。

规范化处理

JavaScript
function normalizeScopedSlots(
  slots: { [key: string]: Function } | void,
  normalSlots: { [key: string]: Array<VNode> }
): { [key: string]: Function } {
  let normalizedSlots
  if (!slots) {
    normalizedSlots = {}
  } else {
    normalizedSlots = slots
  }
  
  // 将普通插槽包装为函数
  for (const name in normalSlots) {
    if (!(name in normalizedSlots)) {
      normalizedSlots[name] = function() {
        return normalSlots[name]
      }
    }
  }
  
  return normalizedSlots
}

统一将普通插槽包装为返回VNode的函数,简化内部处理。

插槽更新机制

JavaScript
// 父组件数据变化
this.msg = 'new value'

// 触发重新渲染
// 1. 父组件render函数执行
// 2. 生成新的插槽VNode
// 3. 子组件$slots/$scopedSlots更新
// 4. 子组件重新渲染

父组件响应式数据变化触发重新渲染,生成新插槽VNode传递给子组件。

作用域插槽编译结果

JavaScript
// 模板
<List :items="users" v-slot="{ item }">
  <div>{{ item.name }}</div>
</List>

// List组件内部
render(h) {
  return h('div', this.items.map(item =>
    this.$scopedSlots.default({ item })
  ))
}

// 执行过程
this.$scopedSlots.default({ item: { name: '张三' } })
// 返回: h('div', [_v(_s(item.name))])
// VNode: { tag: 'div', children: [{ text: '张三' }] }

作用域插槽函数执行时传入子组件数据,返回父组件定义的VNode。

要点总结

  • 普通插槽是预渲染VNode数组,作用域插槽是返回VNode的函数
  • 父组件将插槽内容编译为函数存储在slots选项
  • 子组件通过 $slots 获取VNode,通过 $scopedSlots 执行函数
  • 作用域插槽支持子组件向父组件传递数据
  • normalizeScopedSlots 统一将普通插槽包装为函数
  • 父组件数据变化触发重新渲染,更新插槽VNode传递给子组件

📝 发现内容有误?点击此处直接编辑

← 上一篇 响应式系统与依赖追踪
下一篇 → 组件化与渲染函数底层实现
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库