关于Vue/React中Diffing算法以及key的作用

目录

[1. DIffing算法](#1. DIffing算法)

[1.1 React中的Diffing算法](#1.1 React中的Diffing算法)

[1.2 Vue中的Diffing算法](#1.2 Vue中的Diffing算法)

[1.3 两者对比](#1.3 两者对比)

[2. React/Vue中key的作用(key的内部原理)](#2. React/Vue中key的作用(key的内部原理))

[3. 正确解决方案](#3. 正确解决方案)


最近在学习React和复习Vue相关知识的时候,又重新学习到了两者在循环遍历时key的相关知识内容,正好记录复习一下。

1. DIffing算法

首先React和Vue作为两个前端目前比较热门的框架,两个框架都实现的是将虚拟DOM转换为真实DOM并渲染到页面上,并且有着相当高的效率,一提到效率我们就得先讲解一下两者框架的虚拟DOM中的Diffing算法。

1.1 React中的Diffing算法

React 采用的是一种叫做 虚拟 DOM (Virtual DOM) 的技术,在进行 UI 更新时,React 会先创建一棵虚拟 DOM 树,然后在更新时通过 diffing 算法比较旧的虚拟 DOM 和新的虚拟 DOM。React 的 diffing 算法遵循一些优化原则来高效地进行比较。

核心原则

  1. 单向数据流:

React 通过单向数据流来简化 diffing 操作,即所有组件的状态和属性变化都会导致重新渲染。这使得 React 可以将整个应用的变化分解成小的组件更新,方便进行优化。

  1. 元素类型不同:

如果新旧两个虚拟 DOM 节点的类型不同,React 会直接销毁旧节点并创建一个新节点。例如,如果你从 <div> 改为 <span>,React 就会认为它们是两个完全不同的元素。

  1. 相同类型的元素比较子节点:

如果新旧节点类型相同,React 会比较它们的子节点。React 通过 深度优先遍历 来更新树的不同部分,递归地比较子节点,进行最小更新。

  1. 节点复用与排序优化:

React 假设相同类型的元素之间不会改变顺序。因此,对于相同类型的兄弟节点,React 会尽量复用它们。如果节点的顺序变化较大,React 会做出必要的调整来减少重渲染。

  1. Keys:

React 使用 key 来标识节点在列表中的位置,key 主要用于优化同级元素的更新。当有 key 时,React 会利用它们来决定哪些节点可以复用,从而避免不必要的删除和插入操作。

Diffing 过程简述:

• React 首先通过 浅比较 组件的 props 和 state,决定是否需要更新。

• 如果更新必要,React 会根据节点类型判断是否是完全不同的节点(例如标签不同)还是只是部分变化(例如文本内容变化)。

• 对于相同类型的节点,React 会比较子节点,并递归处理每一层的变化,采用 O(n) 的时间复杂度进行节点更新。

1.2 Vue中的Diffing算法

Vue 的 diffing 算法也基于虚拟 DOM,但与 React 略有不同,Vue 的 diffing 更加注重性能优化,尤其是在节点更新的过程中。Vue 的实现依赖于其响应式系统和模板编译器,因此它的 diffing 算法可能会和 React 在一些细节上有所区别。

核心原则

  1. 虚拟 DOM 与响应式系统:

Vue 通过数据驱动视图的更新,采用响应式系统,当数据变化时,Vue 会自动触发组件的更新。Vue 的 diffing 算法基于它的 虚拟 DOM 实现,Vue 会在每次更新时构建一个新的虚拟 DOM 树,并与旧的虚拟 DOM 树进行对比。

  1. 浅比较:

Vue 采用的是 浅比较,与 React 类似。当虚拟 DOM 中的节点发生变化时,Vue 会只对节点的属性、事件等进行比较。如果节点类型不同,Vue 会重新创建节点。

  1. "就地更新"与"节点复用":

Vue 使用了一种 就地更新 的策略,尽量避免无意义的 DOM 操作。如果某些节点的变化不会影响 DOM,Vue 会选择不更新这些节点,只更新发生变化的部分。Vue 会尽量保留现有节点,避免不必要的重渲染。

  1. 动态节点与静态节点分离:

Vue 会对节点进行分类,将 静态节点 和 动态节点 分离。静态节点不会被重新渲染,而动态节点会根据数据的变化进行更新。Vue 会通过 树的比较 来优化更新过程,从而减少不必要的操作。

  1. Keys:

Vue 也支持使用 key 来优化 diffing 算法。当同一层级的节点发生变化时,Vue 会通过 key 来识别并更新节点的位置,以便提高渲染效率。

Diffing 过程简述:

• Vue 会通过构建新的虚拟 DOM 树并与旧的虚拟 DOM 树进行比较。

• 在比较过程中,Vue 会对比节点的 类型、属性、事件等,如果类型不同,Vue 会销毁旧节点并创建新节点。

• 对于相同类型的节点,Vue 会利用它的 优化策略(如静态节点的保留、动态节点的更新)来决定哪些节点需要更新。

• Vue 会依赖 key 来帮助优化同级节点的更新。

1.3 两者对比

特性 React Vue
虚拟 DOM 是的,React 使用虚拟 DOM,进行最小化的 DOM 更新 是的,Vue 使用虚拟 DOM,并通过响应式系统触发更新
浅比较 是的,React 通过浅比较来决定是否更新节点 是的,Vue 也通过浅比较来判断节点的变化
节点类型变化 类型不同的节点会完全重新渲染 同 React,类型不同的节点会完全重新渲染
子节点比较 对于相同类型的节点,会比较子节点并递归更新 同样,Vue 会递归比较子节点,尤其注重静态与动态节点的区分
key 的使用 使用 key 来优化列表更新,帮助避免不必要的 DOM 操作 使用 key 来优化同层节点的更新
更新优化 React 假设节点顺序不会频繁变化,利用双指针算法减少更新 Vue 更加注重节点复用和静态节点的分离,优化树的

2. React/Vue中key的作用(key的内部原理)

当数据发生变化时,React会根据新的数据生成新的虚拟DOM,随后React进行diff算法比较,主要规则如下:

a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:

(1) 若虚拟DOM中内容没变,直接使用之前的真实DOM

(2) 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉旧的真实DOM

b. 旧虚拟DOM中未找到与新虚拟DOM相同的key:

根据数据创建新的真实DOM,随后渲染到页面

但是如果我们简单的使用index作为key的话,那么请看以下代码:

javascript 复制代码
class Person extends React.Component {
        state = {
            persons: [
                {id:1, name: 'stephen', age:18},
                {id:2, name: 'Jack', age:21}
            ]
        }

        render() {
            return (
                <div>
                    <h2>Show Informations</h2>
                    <button onClick={this.add}>Add James</button>
                    <ul>
                        {
                            this.state.persons.map((person, index) => {
                                return <li key={index}>{person.name} -- {person.age}</li>
                            })
                        }
                    </ul>
                </div>
            )
        }

        add = () => {
            const {persons} = this.state
            const p = {id: persons.length+1, name: 'James', age: 20}
            {/* 注意此时的添加顺序是逆序的 */}
            this.setState({persons: [p, ...persons]})
        }
    }

    ReactDOM.render(<Person/>, document.getElementById('test'))

并且请注意我此时添加的顺序是逆序的,那么React在生成虚拟DOM进行diffing算法对比时就会出现问题:

当进行Diffing对比时,按照规则对比key时,发现对应不上,也就是说React又重新帮我们重新创建出三个虚拟DOM,其实我们只添加了一条数据但是却要创建多个虚拟DOM,如果我们本身的数据量很大,那么带来了一定的资源浪费,这是我们需要避免的。

但是其实有人也发现了:那如果我不逆序添加不就没有问题了吗,对于上述例子来说这种说法是正确的。但是问题又来了,这仅仅是数据的展示,如果每一个<li>标签下又存在一个<input>标签,我们需要为每一个<li>标签添加对应的<input>值,如果此时我们使用index作为key,后续生成新的DOM时会发现原先对应的数据顺序全部混乱,这在我们开发时是十分重大错误。产生这个原因在于我上述所讲到的React 在diffing算法时的原理,按照每一个标签进行渲染。

3. 正确解决方案

我们在开发过程中使用key尽量避免用index以免产生错误或者效率问题。我们应该选用数据的唯一标识,例如后端服务器传回来的data中,可能会含有Primary Key的字段,那么我们可以使用该字段作为key来避免一些问题的出现,以下是合理代码的改造:

javascript 复制代码
class Person extends React.Component {
        state = {
            persons: [
                {id:1, name: 'stephen', age:18},
                {id:2, name: 'jack', age:21}
            ]
        }

        render() {
            return (
                <div>
                    <h2>Show Informations</h2>
                    <button onClick={this.add}>Add James</button>
                    <ul>
                        {
                            this.state.persons.map((person) => {
                                return <li key={person.id}>{person.name} -- {person.age}</li>
                            })
                        }
                    </ul>
                </div>
            )
        }

        add = () => {
            const {persons} = this.state
            const p = {id: persons.length+1, name: 'James', age: 20}
            this.setState({persons: [p, ...persons]})
        }
    }

    ReactDOM.render(<Person/>, document.getElementById('test'))
相关推荐
半个落月1 小时前
LangSmith 全链路观测(下):用 Dataset 与 LLM Judge 量化评估 RA
javascript·langchain
半个落月1 小时前
LangSmith 全链路观测(上):用 LangGraph 与 Milvus 构建可追踪的 RAG 客服
javascript·langchain
Java后端的Ai之路2 小时前
06-React-Props
前端·react.js·前端框架
半个落月2 小时前
LangChain.js Agent 中间件实战:生命周期、动态工具与文件权限
javascript·langchain
志尊宝3 小时前
Vue3 零基础每日笔记(082):文件上传实战——el-upload 校验、自定义上传、头像预览
前端·javascript·vue.js·笔记·#javascript·#vue.js
bmxy小明同学9 小时前
2026-10-11-幻觉治理
服务器·前端·javascript
m0_587383009 小时前
无人自助拼豆系统落地开发实战指南 核心逻辑实现与部署经验分享
前端·javascript·vue.js·人工智能·经验分享·需求分析
zhaiyy10 小时前
HTML Popover API 实践
前端·javascript·html
OpsEye10 小时前
企业 AI 管理新需求:不仅要接入模型,还要看懂每一次调用
javascript·ai编程
牧艺10 小时前
SPA 也能像原生 App:View Transitions API 从原理到可上线实战
前端·javascript·css