插槽与作用域插槽底层实现
插槽本质是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传递给子组件
📝 发现内容有误?点击此处直接编辑