17.3 静态提升
静态提升(hoistStatic)把纯静态节点「提升」到渲染函数之外,渲染函数重新执行时不再重新创建它们,从而减少更新时创建虚拟 DOM 的性能开销与内存占用。
一、问题:静态节点被反复创建
<div>
<p>static text</p>
<p>{{ title }}</p>
</div>
没有静态提升时:
function render(ctx) {
return (openBlock(), createBlock('div', null, [
createVNode('p', null, 'static text'),
createVNode('p', null, ctx.title, PatchFlags.TEXT),
]))
}
title 变化时整个渲染函数重新执行,并产生新的虚拟 DOM 树;纯静态的虚拟节点也会被重新创建一次,完全是浪费。
二、提升后
// 把纯静态节点提升到渲染函数之外
const hoist1 = createVNode('p', null, 'static text')
function render(ctx) {
return (openBlock(), createBlock('div', null, [
hoist1, // 直接引用静态节点
createVNode('p', null, ctx.title, PatchFlags.TEXT),
]))
}
响应式数据变化、渲染函数重新执行时,不会重新创建静态虚拟节点,避免了额外性能开销。
三、纯静态 props 也可提升
<div>
<p foo="bar" a="b">{{ text }}</p>
</div>
// 静态提升的 props 对象
const hoistProp = { foo: 'bar', a: 'b' }
function render(ctx) {
return (openBlock(), createBlock('div', null, [
createVNode('p', hoistProp, ctx.text),
]))
}
四、要点
- 静态提升以树为单位:一整棵不含动态内容的子树会被整体提升;
- 被提升的静态节点使用
PatchFlags.HOISTED(值为-1)标记; - 省下的开销是双份的:少创建一次虚拟节点对象,也少占一份内存。
静态节点本身不会被收集进父级 Block 的
dynamicChildren,所以更新时也不会参与 Diff。
相关
- 17.2.3 Fragment 的稳定性:结构不稳定时放弃优化、回退传统 Diff
- 17.4 预字符串化:静态节点足够多时,进一步把静态提升的节点序列化成字符串