vue3 patch 过程
1 | vue 版本:v3.0.2 |
patch 方法的入口
patch 方法的签名:
1 | patch(n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) |
patch 方法中主要根据新的 vnode 节点的 type 和 shapeFlag 分别调用相对应的方法.
patch 方法源码节选:
1 | switch (type) { |
processText
处理文本节点,判断旧节点是否存在,不存在则创建并插入文本节点,否则从旧节点中获取对应的 dom 元素并设置为新节点的文本。
processCommentNode
处理注释节点,判断旧节点是否存在,不存在则创建并插入注释节点,否则从旧节点中获取对应的 dom 元素设置到新节点上。
Static
静态节点在不同的环境中有一些变化:
生产环境中首次挂载直接在父元素中插入静态节点。以后更新不进行改变,显示了静态节点的不可变。
非生产环境如果新旧节点不一样,会删除旧节点并插入新节点。新旧节点不一样的情况可能会发生在手动改变静态节点上。
processFragment
vue2 中组件模板必须在一个根元素中,vue3 取消了这样的限制,通过在多个根元素上包裹一个 Fragement 类型的 vnode 节点实现,而且不会在 dom 中生成对应的元素,只会在父元素中插入 Framement 的子元素。如果只存在一个根元素不会创建 Framement。
该方法会在父元素的首尾分别插入一个文本节点,用来在父元素中插入 Fragment 的 子元素时起定位的作用。然后如果是首次挂载则调用 mountChildren 方法,否则调用 patchChildren 方法。
processElement
首次挂载调用 mountElement,否则调用 patchElement。
mountElement主要做了vnode节点相对应dom元素的创建、插入和vnode的相关hook的调用,eg.vnode挂载hook、vnode上的指令的创建、挂载hookpatchElement主要做了新旧vnode的props的patch和根据新节点是否有动态子节点分别调用patchBlockChildren和patchChildren。TELEPORT
调用teleport的process方法。同样分首次挂载与更新。- 首次挂载根据
prop.to获取目标元素,调用mountChildren在其上插入teleport的子节点。 - 更新根据是否有动态子节点调用
patchBlockChildren或patchChildren。teleport可以设置禁用,如果禁用则移动到未使用teleport一样的位置。SUSPENSE
调用suspense的process方法。首次挂载调用mountSuspense,更新调用patchSuspense。suspense在异步依赖就绪后才会实际渲染相关组件。 mountSuspense中,设置suspense.pendingBranch为异步组件,使异步组件在一个未插入到文档中的dom元素中patch,patch过程中会执行异步请求,异步请求就绪后才会执行实际的组件挂载 ;在异步就绪之前,先渲染fallback,并设置suspense.activeBranch为fallback。patchSuspense中如果新旧suspense为sameVnode则则执行patch(old, new, ...), 否则卸载旧suspense, 执行patch(null, new, ...)。patch逻辑与mountSuspense基本一致。
patchBlockChildren
遍历每个子 vnode,如果子 vnode 的 type 为 Fragement、Component、teleport,或新旧子节点不为相同的类型,则需要获取它们实际的父节点, 因为可能需要在父节点中进行子节点的增删、替换等操作。最后调用 patch 方法。
patchChildren
根据 vnode 的 patchFlag 和 shapeFlag 区分 keyedChildren 和 unKeydChildren,分别调用 patchKeyedChildren 和 patchUnkeyedChildren,以及新旧节点的有无调用 mountChildren,unmountChildren
patchUnkeyedChildren
依次对子节点进行 patch, 最后如果还有多余的旧子节点则进行卸载,新增的新子节点则进行挂载。
patchKeyedChildren
- 首节点循环比对
- 尾节点循环比对
- 多余的旧子节点进行卸载,新增的新子节点进行挂载
- 创建新子节点的
key的map, 查找是否有旧子节点的key与 新子节点的key相同,如果有相同的key,记录找到的索引,否则查找是否有相同类型的新旧子节点,有则记录找到的索引,没有则卸载当前的旧节点。然后patch; 最后如果有找到的索引,则移动节点至相应的位置,否则挂载新节点。
与 vue2 的不同
vue2在一个大循环中进行节点比对,vue3把每个阶段的比对放在各自的过程中vue3没有进行新旧子节点首尾的交叉比对;vue3对非相同key但相同类型的节点的dom进行了复用vue2以旧子节点为基础进行patch与 节点的移动,vue3以新子节点为基础进行patch与 节点的移动。