17.6 v-once
v-once借助cache数组缓存虚拟 DOM,并暂停动态节点收集,既省掉重新创建 vnode 的开销,也让这段内容彻底不参与 Diff。
一、编译产物
<section>
<div v-once>{{ foo }}</div>
</section>
被 v-once 标记的模板编译为:
function render(ctx, cache) {
return (openBlock(), createBlock('section', null, [
cache[1] || (cache[1] = createVNode('div', null, ctx.foo, PatchFlags.TEXT)),
]))
}
- 第一次渲染时创建虚拟节点并存入
cache; - 之后更新导致渲染函数重新执行时,优先读取缓存,不再重新创建虚拟节点;
- 更新前后虚拟节点完全相同,自然不需要它们参与 Diff。
二、setBlockTracking:暂停动态节点收集
实际编译产物中经常出现下面这段代码:
function render(ctx, cache) {
return (openBlock(), createBlock('div', null, [
cache[1] ||
(setBlockTracking(-1), // 暂停收集:这段 vnode 不会被 Block 收集
(cache[1] = h('div', null, ctx.foo, PatchFlags.TEXT)),
setBlockTracking(1), // 恢复收集
cache[1]), // 整个表达式的值
]))
}
setBlockTracking(-1) 用来暂停动态节点的收集:被 v-once 包裹的动态节点不会被父级 Block 收集,于是组件更新时它自然不会参与 Diff。
三、两点性能收益
- 避免组件更新时重新创建虚拟 DOM:虚拟 DOM 已缓存,更新时无须重新创建;
- 避免无用的 Diff 开销:被
v-once标记的虚拟 DOM 树不会被父级 Block 节点收集,也就不会参与 Diff。
四、适用场景
v-once 通常用于不会发生改变的动态绑定中:
<!-- 常量表达式,内容永远不会变 -->
<div>{{ SOME_CONSTANT }}</div>
<!-- 用 v-once 标记,更新时跳过这段内容 -->
<div v-once>{{ SOME_CONSTANT }}</div>
反向的坑:若被
v-once标记的内容实际会变化,视图将永远停留在首次渲染的结果上。
相关
- 17.5 缓存内联事件处理函数:
cache数组的来源 - 17.3 静态提升:同样是「一次创建、多次复用」的优化思路