Vue3优化靶向更新和模板编译优化

1,020 阅读4分钟

PatchFlags优化

  • Diff算法无法避免新旧虚拟DOM中无用的比较操作,通过patchFlag来标记动态内容,可以实现快捷diff算法

Image.png

我们模拟一个template模板内容被编译成虚拟节点后的内容

Image.png

Image.png

vue处理后的虚拟节点打印结果

Image.png

  • 标识动态节点,也就是dynamic开头的属性里的内容,在上面例子中,我们只比较dynamicChildren里的动态内容

Image.png

  • 而不是去传统的比较children里的内容,也就是无需先去比较h1节点,再去比较span

Image.png

总结

    这样性能就比较好了,不是去进行两棵树,而是去比较某个节点了,这个就是我们所谓的靶向更新,例子中的block有动态收集节点的能力,这个block可以收集所有的动态节点。这样后续更新的时候可以直接跳过静态节点,实现靶向更新

接下来我们去实现一个简易版

  • 我们需要实现openBlock、createElementBlock、toDisplayString、createElementVNode

openBlock

  • 用一个数组来收集多个动态节点

Image.png

createElementBlock

  • 内部还是调创建虚拟节点的方法createVnode,参数比createVnode多了一个patchFlag,传入createVnode,然后在创建的虚拟节点上加patchFlag标识,标识它哪些会变
  • 把createVnode创建好的虚拟节点传入setupBlock方法里,这个方法主要是在虚拟节点上加dynamicChildren,它的值就是当前的currentBlock

Image.png

createElementVNode

  • createElementVNode实际上就是我们之前写过的创建虚拟节点的函数,它俩是一个东西,只是不同的名字

Image.png

toDisplayString

  • 把拿到的值处理成字符串

Image.png

下面我们就进行动态节点的收集了

  • 在创建虚拟节点方法的最后加上判断

Image.png

  • 在创建虚拟节点的时候我们判断如果有currentBlock,并且虚拟节点上有patchFlag大于0,说明有标识,那么我就把当前的的虚拟节点收集到currentBlock里

更新的时候进行处理,判断虚拟节点是否有dynamicChildren

  • 更新流程中patchChildren方法,我们之前这里的实现实际是一个全量的diff算法,其实我们没必要全量diff

Image.png

patchBlockChildren方法

  •  循环新的节点里的dynamicChildren
  • 只去比对新旧虚拟节点中dynamicChildren里的内容

Image.png

  • 以前是树的递归比较,现在是数组的比较,肯定数组的性能好

上述只是元素的靶向更新,下面我们实现props属性的靶向更新

Image.png

  • 这个span就标记了一个2,标识它的class属性可能会发生变化

我们在上述判断dynamicChildren的逻辑前进行判断

Image.png

  • 判断是否匹配到了动态class的标识
  • 以前我们对属性也是全量比对的,现在如果命中了动态class的标识,我们就只笔记新旧虚拟的class,而不是全量的属性比对
  • 类似的我们还可以对style、事件等等去专项的进行判断

如果是不稳定结构

Image.png

  • 当flag改变的时候,p是不会变成div的,那要怎么处理呢

vue3的做法

Image.png

  • 最外层的block收集两个block,p标签看做一个整体,div看做一个整体,这两个block又收集它们各自的动态节点,这就形成了一个block tree
  • 不稳定结构的,都要产生block
  • 每次渲染都会收集对应的block

v-for

  • 如果循环的数据是动态的,会把所有循环子项全部放在一个block里面,里面的子项也就不具备靶向更新的能力了
  • 如果循环的项是固定的,比如我就写死循环3个,那么还是走稳定结构的情况,里面的子项具备靶向更新的能力

总结:

  • v-if和v-for会产生额外的block,防止靶向更新出问题

静态提升

Image.png

Image.png

  • 每次重新渲染调render方法后,我们都要调createElementVNode方法,比较消耗性能

静态提升后

Image.png

  • 把创建静态节点的结构拿出去,每次调render,我就取上次的结构就可以了
  • 上述例子中,我把创建hello所在的标签和创建的静态属性拿了出去

如果你的静态文本超过20个

Image.png

  • 会把所有相同的静态文本字符串化,用的话性能更好

对函数的优化

Image.png

  • 对函数优化前,你每次render后,都创建了一个新函数,对性能是有消耗的

我们可以把函数给缓存起来

Image.png

  • 把函数缓存起来,进行复用

vue3模板编译优化

  • 增添了patchflag来标识哪些节点是动态的
  • block来收集节点,为不稳定结构的也创建了block节点,实现blockTree,做到靶向更新
  • 最后进行优化(静态提升,属性的提升和虚拟节点的提升,函数的缓存,预解析字符串)
  • 写模板的性能会比直接写h函数更好一些

我们后续会详细实现模板编译原理