Vue3中的diff算法——diff算法需要处理的几种场景

大家好,我是哈默。今天我们来继续来学习一下 vue3 中的 diff算法。上回,我们说到了什么时候需要使用 diff 算法。在明确了 diff 算法使用的前提之后,我们再来看下 diff算法 需要处理哪些场景呢?

两组元素个数相同的时候

首先,我们先来看下元素个数相同的时候,比如:

旧节点,ul 元素下是一组 li,总共 3 个 li

js 复制代码
<ul>
  <li>a</li>
  <li>b</li>
  <li>c</li>
</ul>

新节点,ul 元素下也是一组 li,总共也是 3 个 li

js 复制代码
<ul>
  <li>a</li>
  <li>b</li>
  <li>d</li> // 第三个 li 元素发生了变化!👾
</ul>

这个时候,我们只需要遍历两组子节点,然后依次更新每一个节点就可以了,代码如下:

js 复制代码
const oldChildren = ul1.children;
const newChildren = ul2.children;

for (let i = 0; i < oldChildren.length; i++) {
  // 调用 patch 函数依次更新子节点
  patch(oldChildren[i], newChildren[i]);
}

两组元素个数不同的时候

但在实际情况中,我们新的一组元素常常和旧的一组元素个数不一样。

这个时候,就会有 2 种情况:

  1. 新的一组元素数量 > 旧的一组元素数量
  2. 新的一组元素数量 < 旧的一组元素数量

这个时候,我们可以首先获取两组子节点公共的元素数量的长度,然后如果新的一组元素多,则挂载剩余的新元素;新的一组元素少,则卸载旧元素即可。代码如下:

js 复制代码
const oldChildren = ul1.children;
const newChildren = ul2.children;

const oldChildrenLength = oldChildren.length;
const newChildrenLength = newChildren.length;

// 获取公共的长度
const commonLength = Math.min(oldChildrenLength, newChildrenLength);

for (let i = 0; i < commonLength; i++) {
  patch(oldChildren[i], newChildren[i]);
}

// 新的一组元素多,则挂载剩余的新元素
if (newChildrenLength > oldChildrenLength) {
  for (let i = commonLength; i < newChildrenLength; i++) {
    patch(null, newChildren[i]);
  }
}
// 新的一组元素少,则卸载旧元素即可
else if (newChildrenLength < oldChildrenLength) {
  for (let i = commonLength; i < oldChildrenLength; i++) {
    unmount(oldChildren[i]);
  }
}

两组元素的顺序不同

还有一种更加复杂,但是也是很常见的情况,就是新的一组元素的顺序和旧的一组元素的顺序是不同的,比如:

旧节点:

js 复制代码
[
  { type: "p", children: "我是p1" },
  { type: "p", children: "我是p2" },
  { type: "p", children: "我是p3" },
];

新节点:

js 复制代码
[
  { type: "p", children: "我是p2" },
  { type: "p", children: "我是p3" },
  { type: "p", children: "我是p1" },
];

这个时候,最高效的更新节点的方式是:将 我是p1 移动到新的一组节点的末尾即可。

但是,这对于我们肉眼来说,是很简单的。但程序怎么知道谁是 p1 呢?

因为我们现在只用了 type 这个属性来区分不同的节点,这个属性的值可能是 p、div、li...

所以,我们需要引入一个新的属性来区分相同的 typep 的节点,这个新属性就是 key

旧节点:

js 复制代码
[
  { type: "p", children: "我是p1", key: 1 },
  { type: "p", children: "我是p2", key: 2 },
  { type: "p", children: "我是p3", key: 3 },
];

新节点:

js 复制代码
[
  { type: "p", children: "我是p2", key: 2 },
  { type: "p", children: "我是p3", key: 3 },
  { type: "p", children: "我是p1", key: 1 },
];

这个时候,我们就很清晰的知道:对于 p1 来说,它从旧节点的第一位,移动到了新节点的第三位。

总结

那么今天,我们一共探讨了diff算法 需要处理的 3 种不同的情况。

但现在,我们只是从理论的角度进行了分析,但 vue3 中,它具体是如何实现这个 diff算法 的呢?

比如,我们这里最后说的 移动节点,我们可以把 p1 移动到最后,也可以把 p2p3 往前移动,那么 vue3 是如何求得最优的移动方案的呢?这个,我们就留到下一回再说。

相关推荐
风之舞_yjf几秒前
Vue基础(26)_单文件组件
前端·vue.js
weixin_422201301 分钟前
Element Plus中el-tree组件默认选中第一个节点的实现方法
前端·javascript·vue.js
_OP_CHEN2 分钟前
【前端开发之CSS】(六)CSS 弹性布局(Flex)完全指南:从入门到精通,搞定所有布局需求
前端·css·html·flex布局·弹性布局·界面美化·页面开发
LuminescenceJ3 分钟前
RPC通信中的Context上下文如何跨进程传递消息,gRPC为例分析
开发语言·网络·后端·网络协议·rpc·golang
雄狮少年3 分钟前
简单react agent(没有抽象成基类、子类,直接用)--- langgraph workflow版 ------demo1
前端·python·react.js
css趣多多4 分钟前
组件没有原生事件
前端·javascript·vue.js
小小弯_Shelby6 分钟前
el-form表单简洁有效地解决新增与查看详情共用一个页面问题,控制el-form的disabled设置和修改样式
前端·vue.js·elementui
0思必得07 分钟前
[Web自动化] 数据抓取、解析与存储
运维·前端·爬虫·selenium·自动化·web自动化
xiaoxue..10 分钟前
全栈项目 学习日记 (第一章)
前端·react.js·面试·vite
chen_song_10 分钟前
Agent 经典范式构建之 ReAct (Reasoning and Acting): 一种将“思考”和“行动”紧密结合的范式,让智能体边想边做,动态调整
前端·react.js·前端框架