第 66 期 - React 与 Vue 的 Diff 算法异同及列表处理方式对比
摘要
文章阐述了 React 和 Vue 的 Diff 算法的相同点和不同点,包括同层比较、节点比较,重点介绍了两者在列表处理上的差异,还提及了这些算法在性能优化和实际开发中的意义,并分析了 Vue 算法对渲染性能提升效果微弱的原因。
一、整体思路
文章围绕 React 与 Vue 的 Diff 算法展开,先介绍了 Diff 算法设计的基本思路,引出 React 与 Vue 在 Diff 算法上的相似策略,然后从节点比较和列表处理两个方面详细对比了两者的差异,最后总结了这些差异在面试和实际开发中的意义,以及对渲染性能的影响。
(一)Diff 算法设计思路
在设计 Diff 算法时,由于 DOM 操作复杂性高,达到 O(n ^ 3)级别,React 和 Vue 都提出假说以简化算法。假说认为在实际情况中,整棵 DOM 树里父子节点移动的情况较少,所以放弃识别这种情况,从而极大简化算法。
(二)相似策略
1. 同层比较
- 相同层级比较规则:Diff 算法只比较相同层级的节点是否相同。如果相同则直接复用,不需要重新创建;如果不同则默认从该节点开始以下的全部节点都发生了变化,需要重新创建。这意味着在开发过程中,避免跨层级操作节点可提高应用程序性能。
// 示例代码逻辑体现同层比较原则
// 假设这里有两棵虚拟 DOM 树的比较逻辑,若节点 A 在同层级的两棵树中位置和类型相同则复用
function compareSameLevelNodes(oldTree, newTree) {
// 遍历同层级节点进行比较
oldTree.children.forEach((oldNode, index) => {
const newNode = newTree.children[index];
if (isSameNode(oldNode, newNode)) {
// 复用逻辑
} else {
// 重新创建逻辑
}
});
}
2. 节点比较
- 节点类型比较:
- 在 React 中,会区分默认支持的 DOM 节点和自定义节点。在比较节点是否相同时,先比较节点类型是否相同,例如
div
与span
属于不同节点类型则不是同一个节点,然后比较props
、state
、context
等外部传入的参数是否相同来判断是否为同一个节点。不过在 React 中,props
传入方式类似函数传参,存在即使传入相同值但内存地址不同的情况,所以设计了一些规则来优化性能,如当判定元素节点的父节点未发生变化时就不比较props
,还设计了React.memo
这个 API 改变props
比较规则。 - 在 Vue 中,通过
sameVnode
方法比较节点是否相同,主要对key
和标签名做比较,还包括一些其他条件如是否为注释节点、是否定义了data
、当标签为input
时type
是否相同等。相比之下,Vue 对 Diff 算法依赖较弱,更多是通过依赖收集的方式找到需要更新的节点,所以节点比较逻辑相对简单直接。
- 在 React 中,会区分默认支持的 DOM 节点和自定义节点。在比较节点是否相同时,先比较节点类型是否相同,例如
// React 示例代码体现节点类型比较
function ReactNodeComparison(oldNode, newNode) {
if (oldNode.type!== newNode.type) {
return false;
}
// 比较 props 等其他逻辑
if (oldNode.props && newNode.props) {
// 处理 props 比较逻辑
}
return true;
}
// Vue 示例代码体现节点比较
function VueNodeComparison(a, b) {
return (
a.key === b.key &&
a.asyncFactory === b.asyncFactory && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
isUndef(b.asyncFactory.error)
)
)
);
}
(三)列表处理差异
- 共同技术手段:
- 列表是性能问题频发的重要场景,React 和 Vue 都针对长列表设计了特殊的处理方式,都采用给列表中的每一个节点引入唯一
key
值的技术手段。这样可以在旧列表中找到新列表的节点是否已经存在,从而决定是移动节点的位置还是创建新的节点。需要注意尽量不要使用递增的数字序列(如index
)来表示key
值,因为当列表有新增内容时,index
作为key
会导致渲染结果出现混乱。
- 列表是性能问题频发的重要场景,React 和 Vue 都针对长列表设计了特殊的处理方式,都采用给列表中的每一个节点引入唯一
// 示例代码展示使用 key 值的情况
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
- React 的比较方式:
- React 在比较列表节点时,会创建一个变量
lastIndex
(默认值为 0)和一个指针index
。在比较过程中,lastIndex
的变化规则为lastIndex = max(index, lastIndex)
。通过多个案例详细介绍了 React 的比较规则,如在新旧列表节点对比时,根据index
与lastIndex
的关系判断节点是复用还是移动。
- React 在比较列表节点时,会创建一个变量
// React 列表比较示例代码
let lastIndex = 0;
newList.forEach((newNode, index) => {
const oldNode = findNodeInOldList(newNode.key);
if (oldNode) {
if (index < lastIndex) {
// 移动节点逻辑
} else {
// 复用节点逻辑
}
lastIndex = Math.max(index, lastIndex);
} else {
// 创建新节点逻辑
}
});
- Vue 的比较方式:
- 双端比较(Vue2):Vue 为了在特定场景下减少真实 DOM 的移动次数,使用双端比较以及最长子序列递增算法(Vue2)。双端比较会使用 4 个指针分别记录旧列表和新列表的首尾两个节点位置,比较规则遵循首 - 首比较,尾 - 尾比较,尾 - 首比较,首 - 尾比较的顺序,通过这样的方式找出首尾是否有节点可以被复用。如果通过双端比较没找到可复用的节点,还需要重新完整遍历查找。
- Vue3 处理方式:先处理左侧相同部分,再处理右侧相同部分,锁定可复用的节点之后,再针对中间乱序部分,采用最长递增子序列的算法,计算出乱序部分可以复用的最长连续节点。
(四)性能优化及实际意义
- 在同一次事件循环中,由于浏览器在渲染时会对 DOM 树进行统一处理和绘制,所以节点移动次数在一帧中的最终结果相同的情况下,计算成本实际上没有太大区别,所以作者认为 Vue 这种算法对于渲染性能的提升效果应该是非常微弱的。文章中对 React 与 Vue 的 Diff 算法对比主要用于应对面试场景,在实践应用场景中用处不是很大。
(五)总结与推荐
- 文章最后总结了 React 和 Vue 在 Diff 算法上的异同,还推荐了一些学习资源,如成为 React 高手推荐阅读
React 哲学
,有大量可演示案例可沉浸式学习推荐阅读React 19
,并介绍了关注公众号可获取高级前端、算法学习路线、大厂简历编写指南、大厂面试题等内容。
扩展阅读
Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有