插槽实现原理
插槽本质是特殊的prop传递,Vue通过 $slots 和 $scopedSlots 实现内容分发与作用域数据绑定。
插槽编译过程
JavaScript
// 模板: <slot name="header" :data="msg"/>
// 编译为渲染函数
function render() {
return this.$scopedSlots.header({ data: this.msg })
}
具名插槽和作用域插槽最终都编译为 $scopedSlots 的函数调用。
$slots 与 $scopedSlots
JavaScript
// 普通插槽: <slot/>
vm.$slots = {
default: [VNode, VNode] // 预渲染的VNode数组
}
// 作用域插槽: <slot :data="msg"/>
vm.$scopedSlots = {
default: function(props) {
// 返回VNode的函数,props由父组件传入
}
}
普通插槽是预渲染的VNode数组,作用域插槽是返回VNode的函数。
父组件传递插槽内容
JavaScript
// 父组件
createElement(Child, {}, [
createElement('div', '默认内容')
])
// 等价于
h(Child, {}, {
default: () => h('div', '默认内容')
})
父组件通过第三个参数或slots选项传递插槽内容。
子组件渲染插槽
JavaScript
// 子组件内部
Vue.component('Child', {
render(h) {
return h('div', [
this.$slots.default, // 普通插槽
this.$scopedSlots.header?.({ msg: 'hello' }) // 作用域插槽
])
}
})
子组件通过访问 $slots 和 $scopedSlots 渲染传入内容。
作用域插槽数据流
JavaScript
// 子组件定义
{
props: ['items'],
render(h) {
return h('ul', [
this.$scopedSlots.default({
items: this.items, // 将数据传给父组件
index: 0
})
])
}
}
// 父组件使用
<Child :items="list" v-slot="{ items, index }">
<li>{{ items[index] }}</li>
</Child>
子组件将数据作为参数传入插槽函数,父组件通过解构接收。
插槽VNode生成
JavaScript
function normalizeSlot(name, props, scopedSlots) {
const slotFn = scopedSlots[name]
if (slotFn) {
return slotFn(props) // 执行函数获取VNode
}
return null
}
作用域插槽通过执行函数并传入子组件数据获取VNode。
要点总结
- 插槽本质是prop传递,普通插槽是VNode数组,作用域插槽是函数
$slots存储预渲染VNode,$scopedSlots存储返回VNode的函数- 父组件传递插槽内容,子组件通过
$slots/$scopedSlots访问 - 作用域插槽实现子组件向父组件传递数据
- 插槽编译后统一为
$scopedSlots函数调用
📝 发现内容有误?点击此处直接编辑