十分钟掌握Vue3模板编译优化
编译流程简析
- 将模板进行词法分析,转化为对应的ast树(JS描述对象,与虚拟DOM原理差不多)。
- 转换流程
transform
,对动态节点做一些标记- 标记动态节点(Block):指令、事件、插槽、动态属性、模板语法等,渲染时进行动态节点比对即可(靶向更新)。
- 标记节点动态类型(patchFlag),后续更新时只更新该部分即可,减少比对内容(如文本、class)。
- 生成代码
codegen
-- 虚拟dom - 经过
render
方法将该虚拟dom挂载到宿主元素上 render
时直接比对动态节点。
/**
* 当template编译时,遇到动态节点时,会创建动态节点数组dynamicChildren
* 后续遇到普通的动态节点,则收集进dynamicChildren
* 当遇到可能改变结构的动态节点(v-if/v-for/v-else)等
* 则新增block节点(载体),将该特殊节点塞进其dynamicChildren中。
* 形成blockTree
* 进行靶向更新
*
* 当template经过编译转成vnode时
* 渲染时虚拟节点时若遇到patchFlag、dynamicChildren等标记
* 则进行对应的优化处理
*/
let vnode = {};
vnode.dynamicChildren = [
dyChildren1:{}, // 动态子节点
dyChildren2:{},
block1:[
dyChildren1:{},
dyChildren2:{}
]
]
编译流程优化
-
动态节点标记
-
Block
&Block Tree
-
前置:diff算法的特点是同级对比后递归遍历全量比对,性能相对不好。
-
Block
收集子孙动态节点,其原理类似组件收集生命周期钩子- 渲染组件/元素的时候,根据根节点,创建Block对象
h(Fragment)
。- 在创建其子孙虚拟节点时,若该节点有动态部分
createVnode中判断patchFlag
- 收集至该对象的
dynamicChildren
数组中
- 在创建其子孙虚拟节点时,若该节点有动态部分
- 后续比对操作diff时用,渲染不用,不用担心层级问题
- 若新孩子有
dynamicChildren
,直接比对两个动态节点数组动态节点数组,只有一级,无需递归tree=>array
- 若新孩子无,则全量比对两个children,更新即可。
- 若新孩子有
- 渲染组件/元素的时候,根据根节点,创建Block对象
-
Block Tree
=>不稳定的节点序列结构- 如果元素属性可能会影响元素结构
如:vif/v-else/v-once/v-for
,则该Block可能无法准确描述新旧Block
的情况,无法用于比对 - 因此对于可能影响元素结构的,会为其新增一个
Fragment
并创建一个Block,可能会赋予不同的key值标识每个Block
。避免数量不对时,与其他动态节点成同级比较导致错误 - 因此根元素及其子孙,可能会形成多个
Block
组成的树BlockTree
- 且父亲会收集儿子的
Block入dynamicChildren
中 - 父亲更新时会更新其
dynamicChildren
中的元素
- 如果元素属性可能会影响元素结构
-
后续比对操作:其父亲比对两个block时,会走全量diff
无法确定block中的孩子结构,无法标记
。 -
特殊情况:若v-for中的值为常量
v-for="item in 3"
,则不为其创建Block
,直接走靶向更新。
-
-
patchFlag标靶
- 对于动态节点的动态部分进行标记描述,后面节点比对时,直接根据类型处理即可。
- 与元素组合的标记同理
shapeFlag
,都是基于位运算符。 - 在
transform
时标记的
-
-
静态提升
- 静态节点提取为变量
优先从缓存加载:对于静态节点,vue3对将其标记并提取为一个变量,当后续重复渲染时,直接使用该变量即可,不用重复创建虚拟节点。
- 静态属性提取为变量
原理同上
- 高重复量静态节点一次性统一创建。
当多个静态节点一致,则有可能会触发批量创建虚拟节点(>20)
。 - 缓存事件回调提取,防止重新创建事件,复用变量
@click="()=>{}"
。
- 静态节点提取为变量
模板编译优化总结
- 动态节点优化
- 通过
patchFlag
标识节点动态部分,也因此可以识别所有的动态节点,将其提取出来,不用递归做节点diff。平级替换 - 通过
block
收集动态节点,而其中有可能影响结构的指令,则再创建block
将其稳定住,也因此形成了blockTree
,实现靶向更新。
- 通过
- 静态节点优化
- 节点提升、节点批量创建、节点属性提升、函数提升
- 结论:
jsx
是更加灵活的渲染语法renderh('xx',{},'xxx'))]
,但是这种语法没有优化。template
语法更加的简单,且会经过模板编译优化,但书写不灵活。
转载自:https://juejin.cn/post/7241778027876859965