17.1.3 收集动态节点
编译器生成的渲染函数不直接写虚拟节点数据结构,而是调用
createVNode/createBlock等辅助函数;动态节点的收集就发生在这些函数内部。
一、辅助函数而非数据结构
编译器生成的是创建虚拟 DOM 节点的辅助函数调用:
createVNode('p', { class: 'foo' }, null, PatchFlags.TEXT /* patch flag */)
第四个参数就是补丁标志,它表示当前虚拟 DOM 节点是动态节点,且动态因素是「具有动态的文本子节点」。
二、用栈收集动态节点
要让外层 Block 收集到内层动态节点,需要一个栈结构临时存放内层的动态节点集合:
// 动态节点栈
const dynamicChildrenStack = []
// 当前动态节点集合
let currentDynamicChildren = null
// 创建一个新的动态节点集合,并入栈
function openBlock() {
dynamicChildrenStack.push((currentDynamicChildren = []))
}
// 将 openBlock 创建的集合出栈
function closeBlock() {
currentDynamicChildren = dynamicChildrenStack.pop()
}
function createVNode(tag, props, children, flags) {
const key = props && props.key
props && delete props.key
const vnode = { tag, props, children, key, patchFlags: flags }
if (typeof flags !== 'undefined' && currentDynamicChildren) {
// 有补丁标志 → 动态节点,加入当前集合
currentDynamicChildren.push(vnode)
}
return vnode
}
function createBlock(tag, props, children) {
// block 本质上也是一个 vnode
const block = createVNode(tag, props, children)
// 当前动态节点集合就是它的 dynamicChildren
block.dynamicChildren = currentDynamicChildren
// 关闭 block
closeBlock()
return block
}
三、编译产物
// 原模板
// <div>
// <p class="foo">{{ text }}</p>
// <p class="bar"></p>
// </div>
render() {
return (openBlock(), createBlock('div', null, [
createVNode('p', { class: 'foo' }, null, PatchFlags.TEXT /* patch flag */),
createVNode('p', { class: 'bar' }, null),
]))
}
- 用
createBlock代替createVNode来创建 Block; - 每次调用
createBlock之前先调用openBlock(逗号表达式的写法保证顺序),调用之后createBlock内部会closeBlock; createBlock创建 Block 时,把「当前动态节点集合」赋给block.dynamicChildren;- 没有补丁标志的节点(第二个
p)不会进集合。
createVNode的调用是层层嵌套的,内层先执行、外层后执行:内层的openBlock把新集合压栈,内层动态节点被推入内层集合,内层closeBlock出栈后currentDynamicChildren又指回外层集合——这正是用栈的原因。
相关
- 17.1.2 Block 与 PatchFlags:Block 与补丁标志的概念
- 17.1.4 渲染器的运行时支持:渲染器如何消费
dynamicChildren与patchFlags