likes
comments
collection
share

Vue3中的diff算法优化

作者站长头像
站长
· 阅读数 36

前言

之前的文章中已经讲到vue2中的diff算法主要包括五种对比方式,是针对于vue2中数组更新的七种方式而定的,但是vue3中支持数组更新的方式是包括数组原型上的所有方法,同时vue3diff算法的优化不仅仅解决更多的适配方法,同时也是更迅速的进行组件的更新。

优化方向

1. 节点更新,添加静态标记

2. 数组循环的优化

3. 事件优化

节点更新优化

我们编写的组件有些是动态的数据,而有些则是静态的数据。 所以在vue3中对于动态的数据进行标记的添加,对于静态的数据则是直接利用缓存

举个例子:

<div>
<span>hello</span>
<span>{{state}}</span>
</div>

其中hello是静态的数据可以进行缓存使用,不需要在下次更新时进行比对,而state是动态数据我们需要给它打上标记,以便下次进行更新

静态标记如下:

vue2中的vnode:

Vue3中的diff算法优化

vue3中的Vnode:

Vue3中的diff算法优化

vue3中标记类型有两种方式:

一种是ShapeFlags ,一种是# patchFlags

shapeFLag属性使用二进制的方式描述了组件的类型。

export ShapeFlags ={
      ELEMENT = 1, // 普通的HTML元素
      FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件
      STATEFUL_COMPONENT = 1 << 2,  // 有状态组件
      TEXT_CHILDREN = 1 << 3, // 子节点是文本
      ARRAY_CHILDREN = 1 << 4, // 子节点是数组
      SLOTS_CHILDREN = 1 << 5, // 子节点是插槽
      TELEPORT = 1 << 6, // 当前是个teleport组件
      SUSPENSE = 1 << 7, // 当前是个suspense组件
      COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 表示需要被keep-live的有状态组件
      COMPONENT_KEPT_ALIVE = 1 << 9, // 已经被keep-live的有状态组件
}

patchFlag是在编译模板时,给vnode添加的一个标识信息,这个标识信息反映了vnode的哪些部位绑定了动态值,这样在runtime阶段,可以根据patchFlag判断出哪些内容需要更新,实现定点更新。

export  PatchFlags = {
  // 表示vnode具有动态textContent的元素
  TEXT = 1,
      // 表示vnode具有动态的class
      CLASS = 1 << 1,
      // 表示具有动态的style
      STYLE = 1 << 2,
      // 表示具有动态的非class和style的props
      PROPS = 1 << 3,
      // 表示props具有动态的key,与CLASS、STYLE、PROPS冲突
      FULL_PROPS = 1 << 4,
      // 表示有监听事件(在同构期间需要添加)
      HYDRATE_EVENTS = 1 << 5,
      // 表示vnode是个children顺序不会改变的fragment
      STABLE_FRAGMENT = 1 << 6,
      // 表示children带有key的fragment
      KEYED_FRAGMENT = 1 << 7,
      // 表示children没有key的fragment
      UNKEYED_FRAGMENT = 1 << 8,
      // 表示vnode只需要非props的patch。例如只有标签中只有ref或指令
      NEED_PATCH = 1 << 9,
      // 表示vnode存在动态的插槽。例如动态的插槽名
      DYNAMIC_SLOTS = 1 << 10,
      // 表示用户在模板的根级别存在注释而创建的片段,这是一个仅用于开发的标志,因为注释在生产中被剥离
      DEV_ROOT_FRAGMENT = 1 << 11,

      // 以下都是一些特殊的flag,它们不能使用位运算进行匹配
      // 表示vnode经过静态提升
      HOISTED = -1,
      // diff算法应该退出优化模式
      BAIL = -2
}

在编译阶段通过不同的patchflg值进行不同的方法比较

const patchElement = (n1, n2, parentComponent, parentSuspense, isSVG, optimized) => {
  const el = (n2.el = n1.el);
  let { patchFlag, dynamicChildren, dirs } = n2;
  const oldProps = (n1 && n1.props) || EMPTY_OBJ;
  const newProps = n2.props || EMPTY_OBJ;
  let vnodeHook;
  if ((vnodeHook = newProps.onVnodeBeforeUpdate)) {
    invokeVNodeHook(vnodeHook, parentComponent, n2, n1);
  }
  if (dirs) {
    invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate');
  }
  if (__HMR__ && parentComponent && parentComponent.renderUpdated) {
    // HMR updated, force full diff
    patchFlag = 0;
    optimized = false;
    dynamicChildren = null;
  }
  if (patchFlag > 0) {
    // the presence of a patchFlag means this element's render code was
    // generated by the compiler and can take the fast path.
    // in this path old node and new node are guaranteed to have the same shape
    // (i.e. at the exact same position in the source template)
    if (patchFlag & 16 /* FULL_PROPS */) {
      // element props contain dynamic keys, full diff needed
      patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG);
    }
    else {
      // class
      // this flag is matched when the element has dynamic class bindings.
      if (patchFlag & 2 /* CLASS */) {
        if (oldProps.class !== newProps.class) {
          hostPatchProp(el, 'class', null, newProps.class, isSVG);
        }
      }
      // style
      // this flag is matched when the element has dynamic style bindings
      if (patchFlag & 4 /* STYLE */) {
        hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG);
      }
      // props
      // This flag is matched when the element has dynamic prop/attr bindings
      // other than class and style. The keys of dynamic prop/attrs are saved for
      // faster iteration.
      // Note dynamic keys like :[foo]="bar" will cause this optimization to
      // bail out and go through a full diff because we need to unset the old key
      if (patchFlag & 8 /* PROPS */) {
        // if the flag is present then dynamicProps must be non-null
        const propsToUpdate = n2.dynamicProps;
        for (let i = 0; i < propsToUpdate.length; i++) {
          const key = propsToUpdate[i];
          const prev = oldProps[key];
          const next = newProps[key];
          if (prev !== next) {
            hostPatchProp(el, key, prev, next, isSVG, n1.children, parentComponent, parentSuspense, unmountChildren);
          }
        }
      }
    }
    // text
    // This flag is matched when the element has only dynamic text children.
    if (patchFlag & 1 /* TEXT */) {
      if (n1.children !== n2.children) {
        hostSetElementText(el, n2.children);
      }
    }
  }
};

数组循环优化

相对于vue2中for循环的对比,vue3中你的对比则是比较简单的,vue3中做法如下:

  1. 从前往后对比 (头部与头部比较)
  2. 从后往前对比 (尾部与尾部比较)
  3. 基于最长子序列的比较进行-》移动|添加|删除

举个例子:

旧数组:【a,b,c,d,e,f,g】

新数组:【a, b, f, c, d, e, h, g]

首先是开始最简单的比较获取之前的缓存数据:

  1. 首先是头头对比,发现不同就停止本次循环【a,b】
  2. 然后是尾尾比较得到【g】;

经过1.2的比较后得到不同局部数据【f,c,d,e,h】,接下来就是不同数据的比较,这一步就是要使用最长子序列方法进行(移动|添加|删除)的操作。

在源码中使用map函数对数组的值和key进行一个绑定。(有兴趣的同学可以看下源码)

源码中通过 newIndexToOldIndexMap 拿到在数组里对应的下标,生成数组 [ 5, 2, 3, 4, -1 ],-1 是老数组里没有的,没有的数据我们只能往旧节点补充数据。

很明显可以发现在[ 5, 2, 3, 4, -1 ]中【2,3,4】是有序的,并且是依次递增的,此时他对应的节点是【c,d,e】,这个时候我们就很容易的保持【2,3,4】不变进行其他数据的移位,既是在此基础的前方插上一位就能完成新节点的更新

事件优化

举例点击事件:

<div>
  <button  @click="change">hello</button>
</div>

如果开启了缓存处理,事件的变化不会引起重新渲染。

import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", {
      onClick: _cache[1] || (_cache[1] = $event => (_ctx.onClick($event)))
    }, "hello")
  ]))
}
转载自:https://juejin.cn/post/7159488062689378318
评论
请登录