一道 React 面试题,搞懂 React 的 Diff 算法!你以为在更新页面,其实是在「做手术」

87 阅读4分钟

React 面试官常问的一道灵魂题:React 是怎么更新 UI 的?它怎么知道页面哪个地方变了?

你以为这是考你 setState?
不,这其实是在考你 —— React 的 diff 算法理解是否扎实。

👦 前言:我是如何在项目中被“刷新页面”坑过的

还记得刚学 React 那会儿,只要一改数据,整个页面都重新渲染。

“你点个按钮干嘛整个页面都重新加载了啊?”

后来我才知道,没理解 Virtual DOM 和 diff 算法,就是 React 的“盲盒开发” ,啥都靠猜。

直到我认真啃完了 diff 原理,才明白:

“原来 React 更新页面,是在偷偷找不同,做一场精准手术!”


🎯 React diff 是什么?

React diff(差异算法) ,是 React 中用来比较新旧 Virtual DOM 树的机制,目标是:

🧠 找出最少的变化,做最少的 DOM 操作。

所以,每当你 setStateuseState 更新数据,React 会:

  1. 生成一份新的虚拟 DOM
  2. 把新旧虚拟 DOM 对比(diff)
  3. 根据变化,精准更新 DOM(而不是全部重绘)!

🔍 举个 React diff 的经典例子:

<ul>
  <li>A</li>
  <li>B</li>
  <li>C</li>
</ul>

用户操作后:

<ul>
  <li>A</li>
  <li>X</li>
  <li>C</li>
</ul>

你以为 React 会重新渲染整个 <ul>?其实不会。

如果你写得像这样👇(注意 key):

{ list.map(item => <li key={item.id}>{item.name}</li>) }

React 会聪明地只做:

  • BX:更新这个 <li>
  • 其他不变,全部复用!

🧠 React diff 到底做了什么?

React diff 不是传统的全量比对算法(那样太慢),它做了三条性能换正确性的妥协:

🔹 1. 只对比同层级的节点

<div>
  <span>A</span>
</div>

换成:

<div>
  <p>B</p>
</div>

React 只比较 <span><p>不会跨层级深度比对(这样速度更快)。

🔹 2. 节点类型不同 ➜ 直接替换

如果新旧节点的类型不同(比如 <span><p>),React 会直接删掉原来的,再创建新的,不做复用

🔹 3. 使用 key 提高识别准确性

最最重要的一条:React diff 依赖 key

你这样写是对的:

todos.map(todo => (
  <li key={todo.id}>{todo.title}</li>
))

如果你写错了👇:

todos.map((todo, index) => (
  <li key={index}>{todo.title}</li>
))

就容易在增删中间项时,导致组件状态错乱(比如输入框值跳了位置、焦点丢失)。


🧪 小测验:下面这段代码的 key 有什么问题?

<ul>
  {
    items.map((item, index) => (
      <li key={index}>{item.name}</li>
    ))
  }
</ul>

答案:用 index 作为 key,不稳定,当列表发生“插入、删除”时,React 会错误判断元素变化,导致性能下降甚至 UI 闪烁、状态丢失。


小实验:手动触发 diff 流程


function App() {
  const [list, setList] = useState([
    { id: 1, name: 'A' },
    { id: 2, name: 'B' },
    { id: 3, name: 'C' }
  ])

  return (
    <div>
      <button onClick={() => {
        setList([
          { id: 1, name: 'A' },
          { id: 4, name: 'X' },
          { id: 3, name: 'C' }
        ])
      }}>
        替换 B ➜ X
      </button>
      <ul>
        {list.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  )
}

分析:

  • 按钮点击后,B 变成 X,但 id 从 2 ➜ 4;
  • React 检测到 key 变了,直接删除 + 创建,不做复用。

你可以加个 console.loguseEffect 来观察每一项是否重新渲染。

💥 React diff 的底层实现:Fiber 架构

如果你对源码感兴趣,React 的 diff 不再是老版本“递归同步执行”,而是基于 Fiber 架构 的:

  • 支持任务拆分(可以暂停/恢复);
  • 实现异步可中断更新
  • 给每个组件分配 Fiber 节点,记录前后状态。

但对于大多数开发者,了解「diff 做的是“找不同 + 最小更新”」就已经能应付 80% 的场景和面试题了!


📌 面试官可能会这样问你:

🧑‍💻 面试官:React 为什么推荐你使用 keykey 有什么作用?

答法推荐:

  • React 使用 key 来判断哪些 DOM 节点可以复用;
  • 减少重排重绘,提高性能;
  • 使用 index 作为 key 可能导致状态错乱;
  • React diff 的核心思想是最小化 DOM 操作。

✅ 总结一下本文核心知识:

关键点说明
diff 是干嘛的比较新旧虚拟 DOM,最小化 DOM 更新
不做全量对比同层级对比、类型不同直接替换
key 的重要性提高识别效率,避免状态错乱
不建议用 index 做 key会导致错误复用、UI 闪烁问题
Fiber 是新架构支持异步、时间切片、可中断渲染

🚀 结语:写 React,别只会 setState,得懂它背后的“找不同”机制!

React diff 是 React 性能优化的基础。理解了 diff,你才真正走进了 React 的内功世界。

如果你面试时说出上面那些点,面试官一定会在心里想: “这哥们,不止能写页面,还懂框架底层,稳!”


如果你觉得本文有帮助,不妨点赞收藏 + 关注,一起卷进更深的前端知识世界吧!