提示:看到我 请让滚去学习
vue3编译模版的提升
文章目录
- vue3编译模版的提升
- 静态节点提升
- 补丁标志和block的使用
- 附录:
template explorer可以将我们的源模版转化成渲染函数代码,vue2中就有,而Vue3 template explorer 功能更加丰富。
静态节点提升
当我们开启静态提升,可以发现静态节点确实被从渲染函数中提升以便可以在每个渲染器上重用它,所以在每次组件更新时,我们都会重新调用render函数,但是_hoisted_1都将被重用。
这样有两点好处:
1.避免重新创建对象,然后删除
2.在模版算法中,当看到两个节点在同一位置时,在严格平等的情况下,我们可以跳过它,因为我们知道它永远不会改变
补丁标志和block的使用
Vue 3 template explorer中当我们绑定一个监听器,编译器会生成一个补丁标志,表明这个节点有动态props需要修补下,以及需要修补的名称是onclick。通常使用简单的虚拟dom渲染算法,在div上绑定的元素,整个对象必须作为一个整体来diff,所以如果div上绑定了一个静态的id属性,我们还是会对比整个对象,确保它不会改变,因为运行时并不能确定它是否改变。但是vue3的编译器通过补丁和关键字结合可以为运行时提供足够信息,确定可以改变的是哪些属性。所以渲染是可以跳过编译器那些推断的永远不会改变的props。
上述在虚拟dom发生改变时,不会检查整个节点的所有属性方法,而是结合补丁标志检查可改变的东西。但实际上,我们更多时候绑定一个事件监听器,并不会更改事件处理程序,所以vue3默认开启cacheHandlers
开启cacheHandlers会把我们的事件变成一个内联函数,并在第一次渲染时将其缓存,在此后渲染中,我们将始终使用同一个内联处理程序,但是里面的函数会访问ctx.onClick,所以即时onClick发生变化,我们不需要对vnode本身做任何事,所以上述代码在更新过程中现在可以完全跳过整个节点。
所有这些在vue2父子组件中,即使子组件什么都没有改变,也会导致所有子组件在父组件重新渲染时,所有接受到那个props的子组件重新渲染,在大型应用中,会引起连锁反应,因为你在向下传递函数,在每次渲染时,都会创建一个新的内联函数,会导致所有收到那个props的子组件重新渲染,所以vue3中使用句柄缓存,可以避免在大型组件树中发生不必要的渲染
当我们调用render函数会生成一个类似以下的vdom
当vdom某些数据发生改变时,渲染器并不知道发生了什么改变,所以它会递归遍历整棵树,进行新旧快照对比,对比新旧节点找出改变了什么,这显然会导致更多的性能问题。
例如上述代码我们每次更新发生变化的只有span标签,编译器会使用_openBlock(),将模版的根变成我们所称的块(block),上图中的_openBlock(),当块打开时,所有的节点表达式都会被评估是否是动态的,当节点创建时携带有补丁表示的东西(动态)都会被跟踪,并且添加到当前打开的block作为动态子节点。所以在render方法整个调用后,根div将有一个额外的属性,称为动态子节点,它是只包含了携带补丁标识的子节点的扁平化数组。不管dom节点层级多深,块都只跟踪其上的动态节点。
另外如果使用v-if等结构指令,它会改变节点结构,当v-if值切换时节点会从dom树上消失,所以对于根节点的block,使用v-if的节点下的节点将不再安全。所以v-if节点会变成一个块,这个块会变成父节点的动态快被跟踪,所以有嵌套的块,将在扁平化数组中跟踪他自己的动态子对象。
所以对于整个dom树在跟新时不再需要再检查每个vnode的变化,而是递归去找block,得到block内可能发生变化的信息。
补丁信息本身还编码了关于什么样工作信息,例如TEXT表示在你试图区分这个节点时,只需要检查它的文本内容而不需要关注例如props等其他信息
附录:
vue2在线模版编译器:[https://vue3js.cn/vue-template-explorer/]{.underline}
vue3在线模版编译器:[https://template-explorer.vuejs.org/#]{.underline}