前言:React 到底是怎么"聪明更新"的?
很多人刚学 React,以为它和 Vue 一样"数据变了,视图自动变",但实际上,React 的核心逻辑是:
通过"对比新旧虚拟 DOM",找到不同,再更新真实 DOM。 这个对比过程靠的是一个算法:DOM diff 策略。 但别以为它真的"深度比对"哦,React 为了性能,做了三条超硬核的简化规则,我们今天就来好好说说
一、只对比同一层的节点
规则 1:不跨层比较,只比较同层节点
React 更新时,只在"原来在第几层"的节点上去找对应的新节点,不往下深挖结构。
示例 1:改了结构,React会直接放弃旧节点
less
// 旧结构
<div>
<p>Hello</p>
</div>
// 新结构
<p>
<div>Hello</div>
</p>
你只是把标签换了一下嵌套层级,React 看到:
<div>
变成<p>
,不在同一层了- React 根本不比内容,直接卸载
<div>
,新建<p>
为什么这样设计?
因为对比层级结构是非常耗性能的,React 的目标是快!
所以它直接假设:"你要是变了层级,我就不去比,直接干掉重建!"
面试提示:
Q:React 为什么不进行跨层级 diff?
A:为了性能优化,React diff 算法采用只比同层节点的策略。跨层结构变化通常会导致全量更新。
二、节点类型不一致,直接干掉重建
规则 2:不同类型的组件/标签,不尝试复用,直接销毁旧节点
示例 2:组件类型变化 = 直接重建
javascript
{isLogin ? <LoginForm /> : <RegisterForm />}
- 初始渲染:显示
<LoginForm />
- 点击按钮切换:显示
<RegisterForm />
虽然两个组件结构可能一样,但 React 不会去做"结构相似性"比对!
- 它只看组件的类型是否一样
- 类型不一样:旧的销毁 + 新的挂载
- 所以内部状态会丢失(比如表单内容、焦点、动画等)
示例 3:标签类型变化也不复用
ini
// 初始:
<div className="box">内容</div>
// 改成:
<span className="box">内容</span>
虽然内容、class 都一样,但标签从 <div>
变成 <span>
,React 会:
- 销毁
<div>
- 新建
<span>
为什么这样设计?
因为 React 无法判断你这个 <span>
是不是能复用 <div>
的布局行为、事件绑定等,它直接选择保守策略:重新创建 DOM 更安全。
面试提示:
Q:为什么类型变了就不能复用?
A:类型不同(组件或标签),代表语义完全不同,React 采用重建策略避免潜在副作用。
三、列表渲染时,必须使用 key!
规则 3:列表渲染依赖 key 来定位节点变化
这是 React diff 性能的核心优化技巧,也是最多人挂的点。
示例 4:没有 key,React 很"懵"
javascript
const todos = ['吃饭', '学习', '打游戏']
return (
<ul>
{todos.map(todo => <li>{todo}</li>)}
</ul>
)
你没加 key,React 更新列表时:
- 无法知道哪个
<li>
是新加入的 - 只能按顺序猜测复用,很容易猜错
- 导致动画抖动、输入框光标丢失、状态错乱
示例 5:错误地使用 index 当 key(很常见)
javascript
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
你添加一个新 todo 到开头时:
- 所有原来的 index 全部错位
- React 误以为每一项都变了
- 状态全部重置!
正确做法:
ini
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
只要你 key 足够稳定,React 就能:
- 精准知道每个节点是不是新增的
- 哪些能复用
- 哪些需要卸载
面试再提醒:
Q:为什么不能用 index 当 key?
A:当数组顺序变动时,index 会错乱,导致组件误复用,引发 Bug。
总结:React Diff 三大原则(高效但不聪明)
原则 | 含义 | 举例 |
---|---|---|
只比同层节点 | 跨层结构不比,直接重建 | <div><p></p></div> → <p><div></div></p> |
类型不同就重建 | 标签或组件类型变了直接重造 | <div> → <span> ,<A /> → <B /> |
key 是列表灵魂 | 精准定位复用节点,性能好 | key={todo.id} ✅ ,key={index} |
一道大厂面试题:
React 渲染以下列表,新增一项时,为什么动画错位了?该如何优化?
javascript
{list.map((item, index) => (
<li key={index}>{item.text}</li>
))}
答案:
- 使用 index 做 key,新增/删除项会导致 key 错位
- React 错误复用了组件,状态和动画出现问题
- ✅ 应改为
key={item.id}
,保持 key 的唯一和稳定
最后:
React 的 diff 算法是"聪明又傻"的混合体:
- 不搞太多花活(不跨层,不猜结构)
- 靠 key 做精准优化
- 用类型判断是否重建
这是一种偏工程化的务实设计,你了解了它的原则,才能用好它、避开坑、在面试里妙语连珠说出这些:
"React diff 时间复杂度 O(n),key 是复用节点的标识,类型变化直接重建,保证更新过程稳定高效。"