vue3渲染机制、模板编译、渲染函数与JSX

264 阅读5分钟

1、渲染管线

image.png

Vue 组件挂载时会发生如下几件事:

  1. 编译:Vue 模板被编译为渲染函数:即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成,也可以通过使用运行时编译器即时完成。
  2. 挂载:运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。这一步会作为响应式副作用执行,因此它会追踪其中所用到的所有响应式依赖。VDOM ——> DOM

响应式原理可看:juejin.cn/post/710762…

  1. 更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树,将它与旧树进行比较,然后将必要的更新应用到真实 DOM 上去。VDOM进行比较,再转为真实DOM

2、vue2中的模板编译

1. vue2中通过标记静态根节点,来优化diff操作

  • 在构建的时候,将模板编译为render函数,需要编译静态根节点,静态节点

  • 当组件的状态发生变化时,会通知watch

  • 触发watch的undate,最终执行虚拟DOM的patch操作

  • 遍历所有虚拟节点,找到差异最终更新到真实DOM上

vue2 虚拟DOM源码解读可查看:vue2 虚拟DOM源码解析

3、vue3中的模板编译

vue3中标记和提升所有的静态节点,diff的时候只需要对比动态节点内容

1. 对于静态节点:静态提升

  • 静态提升,提升静态节点,对于静态内容,Vue 编译器自动地会提升这部分 vnode 创建函数到这个模板的渲染函数之外,并在每次渲染时都使用这份相同的 vnode,渲染器知道新旧 vnode 在这部分是完全相同的,所以会完全跳过对它们的差异比对。

  • 此外,当有足够多连续的静态元素时,它们还会再被压缩为一个“静态 vnode”,其中包含的是这些节点相应的纯 HTML 字符串。这些静态节点会直接通过 innerHTML 来挂载。同时还会在初次挂载后缓存相应的 DOM 节点。如果这部分内容在应用中其他地方被重用,那么将会使用原生的 cloneNode() 方法来克隆新的 DOM 节点,这会非常高效。

2. 对于动态节点:更新类型标记

  • 对于单个有动态绑定的元素来说,在为这些元素生成渲染函数时,Vue 在 vnode 创建调用中直接编码了每个元素所需的更新类型(patch flag)

  • 一个元素可以有多个更新类型标记,会被合并成一个数字。运行时渲染器也将会使用位运算来检查这些标记,确定相应的更新操作。位运算检查是非常快的。通过这样的更新类型标记,Vue 能够在更新带有动态绑定的元素时做最少的操作。

  • Vue 也为 vnode 的子节点标记了类型。举例来说,包含多个根节点的模板被表示为一个片段 (fragment),大多数情况下,我们可以确定其顺序是永远不变的,所以这部分信息就可以提供给运行时作为一个更新类型标记。运行时会完全跳过对这个根片段中子元素顺序的重新协调过程。

<!-- 仅含 class 绑定 -->
<div :class="{ active }"></div>

<!-- 仅含 id 和 value 绑定 -->
<input :id="id" :value="value">

<!-- 仅含文本子节点 -->
<div>{{ dynamic }}</div>

// 编译为,2就是标记的更新类型
createElementVNode("div", {
  class: _normalizeClass({ active: _ctx.active })
}, null, 2 /* CLASS */)

// fragment片段
export function render() {
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    /* children */
  ], 64 /* STABLE_FRAGMENT */))
}

3. 对于动态节点:会进行 树结构打平

  • 每一个块都会追踪其所有带更新类型标记的后代节点 (不只是直接子节点),编译的结果会被打平为一个数组,仅包含所有动态的后代节点

  • 当这个组件需要重渲染时,只需要遍历这个打平的树而非整棵树。这也就是我们所说的树结构打平,这大大减少了我们在虚拟 DOM 协调时需要遍历的节点数量。模板中任何的静态部分都会被高效地略过。

  • v-if 和 v-for 指令会创建新的区块节点

  • 一个子区块会在父区块的动态子节点数组中被追踪,这为他们的父区块保留了一个稳定的结构。

<div> <!-- root block -->
  <div>...</div>         <!-- 不会追踪 -->
  <div :id="id"></div>   <!-- 要追踪 -->
  <div>                  <!-- 不会追踪 -->
    <div>{{ bar }}</div> <!-- 要追踪 -->
  </div>
</div>

// 打平后
div (block root)
- div 带有 :id 绑定
- div 带有 {{ bar }} 绑定

4. 对SSR激活的影响

更新类型标记和树结构打平都大大提升了 Vue SSR 激活的性能表现:

  • 单个元素的激活可以基于相应 vnode 的更新类型标记走更快的捷径。

  • 在激活时只有区块节点和其动态子节点需要被遍历,这在模板层面上实现更高效的部分激活。

4、说一下vue3如何变得更快?

1. 优化响应式

关于响应式内容:juejin.cn/post/710762…

2. 优化了编译过程中,diff算法

请看上文中vue3模板编译

3. 体积变小

  • vue3 支持 tree-shaking,不需要的不会进行打包,使 vue3 的体积更小。优化后的打包体积只有原来的一半(13kb)。就算把所有的功能都引入进来也只有 23kb,依然比 vue2 更小, keep-alivetransition 或者 v-for 等功能都可以按需引入。

  • vue3 优化了打包方法,使得打包后的 bundle 的体积也更小。官方所给出的一份惊艳的数据:打包大小减少 41% ,初次渲染快 55% ,更新快 133% ,内存使用减少 54%

4. Vue3引入了Composition API

通过提供逻辑组合和重用的方法来提升代码的可读性和重用性。不仅可以让Vue3应用更好地组织和维护业务逻辑,还可以让开发人员更加轻松地实现优化。