Vue3 相比于 Vue2 的有哪些“与众不同”( 三 )

注意第 3 个_createElementVNode的第 4 个参数即patchFlag字段类型 , 字段类型情况如下所示 。1 代表节点为动态文本节点 , 那在 diff 过程中 , 只需比对文本对容 , 无需关注 class、style等 。除此之外 , 发现所有的静态节点 , 都保存为一个变量进行静态提升 , 可在重新渲染时直接引用 , 无需重新创建 。
export const enum PatchFlags {TEXT = 1, // 动态文本内容CLASS = 1 << 1, // 动态类名STYLE = 1 << 2, // 动态样式PROPS = 1 << 3, // 动态属性 , 不包含类名和样式FULL_PROPS = 1 << 4, // 具有动态 key 属性 , 当 key 改变 , 需要进行完整的 diff 比较HYDRATE_EVENTS = 1 << 5, // 带有监听事件的节点STABLE_FRAGMENT = 1 << 6, // 不会改变子节点顺序的 fragmentKEYED_FRAGMENT = 1 << 7, // 带有 key 属性的 fragment 或部分子节点UNKEYED_FRAGMENT = 1 << 8,// 子节点没有 key 的fragmentNEED_PATCH = 1 << 9, // 只会进行非 props 的比较DYNAMIC_SLOTS = 1 << 10, // 动态的插槽HOISTED = -1,// 静态节点 , diff阶段忽略其子节点BAIL = -2 // 代表 diff 应该结束}事件缓存Vue3 的 cacheHandler可在第一次渲染后缓存我们的事件 。相比于 Vue2 无需每次渲染都传递一个新函数 。加一个click事件 。
<div id="app"><h1>技术摸鱼</h1><p>今天天气真不错</p><div>{{name}}</div><span onCLick="() => {}"><span></div>渲染函数如下
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"const _withScopeId = n => (_pushScopeId("scope-id"),n=n(),_popScopeId(),n)const _hoisted_1 = { id: "app" }const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("h1", null, "技术摸鱼", -1 /* HOISTED */))const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("p", null, "今天天气真不错", -1 /* HOISTED */))const _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("span", { onCLick: "() => {}" }, [/*#__PURE__*/_createElementVNode("span")], -1 /* HOISTED */))export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock("div", _hoisted_1, [_hoisted_2,_hoisted_3,_createElementVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */),_hoisted_4]))}Diff 优化搬运 Vue3 patchChildren 源码 。结合上文与源码 , patchFlag帮助 diff 时区分静态节点 , 以及不同类型的动态节点 。一定程度地减少节点本身及其属性的比对 。
function patchChildren(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {// 获取新老孩子节点const c1 = n1 && n1.childrenconst c2 = n2.childrenconst prevShapeFlag = n1 ? n1.shapeFlag : 0const { patchFlag, shapeFlag } = n2// 处理 patchFlag 大于 0if(patchFlag > 0) {if(patchFlag && PatchFlags.KEYED_FRAGMENT) {// 存在 keypatchKeyedChildren()return} els if(patchFlag && PatchFlags.UNKEYED_FRAGMENT) {// 不存在 keypatchUnkeyedChildren()return}}// 匹配是文本节点(静态):移除老节点 , 设置文本节点if(shapeFlag && ShapeFlags.TEXT_CHILDREN) {if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {unmountChildren(c1 as VNode[], parentComponent, parentSuspense)}if (c2 !== c1) {hostSetElementText(container, c2 as string)}} else {// 匹配新老 Vnode 是数组 , 则全量比较;否则移除当前所有的节点if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense,...)} else {unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)}} else {if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {hostSetElementText(container, '')}if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {mountChildren(c2 as VNodeArrayChildren, container,anchor,parentComponent,...)}}}}【Vue3 相比于 Vue2 的有哪些“与众不同”】patchUnkeyedChildren 源码如下 。
function patchUnkeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {c1 = c1 || EMPTY_ARRc2 = c2 || EMPTY_ARRconst oldLength = c1.lengthconst newLength = c2.lengthconst commonLength = Math.min(oldLength, newLength)let ifor(i = 0; i < commonLength; i++) {// 如果新 Vnode 已经挂载 , 则直接 clone 一份 , 否则新建一个节点const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as Vnode)) : normalizeVnode(c2[i])patch()}if(oldLength > newLength) {// 移除多余的节点unmountedChildren()} else {// 创建新的节点mountChildren()}}


推荐阅读