【react 高频面试题—核心原理篇】:既然 React 依靠 Diff 算法对比差异,那它的 Diff 算法是如何实现的?为什么循环时必须加 Key?

46 阅读2分钟

面试官提问:既然 React 依靠 Diff 算法对比差异,那它的 Diff 算法是如何实现的?为什么循环时必须加 Key?

💡 核心回答技巧

不要陷入复杂的源码细节,要抓住 React Diff 的 “三个前提假设(策略)” 。面试官想听的是:React 如何把 O(n3)O(n^3) 的对比复杂度降低到 O(n)O(n)


1. React Diff 的三大策略

传统的树对比算法时间复杂度极高。React 通过以下三个大胆的假设,实现了近乎线性的对比效率:

  • 策略一:分层求异(Tree Diff)

    React 只会对同层级的节点进行比较。如果一个 DOM 节点在更新中跨越了层级,React 不会尝试复用它,而是直接销毁旧的,创建新的。

  • 策略二:组件求异(Component Diff)

    如果是同类型的组件,继续比较;如果不是(比如从

    变成了 ),则直接删除旧组件及其子节点,重新创建。

  • 策略三:节点求异(Element Diff)

    对于同一层级的子节点,React 通过 Key 值来识别节点。


2. 为什么循环时必须加 Key?

这是 Element Diff 中的核心优化。

  • 没有 Key 的情况:React 会采用“就地复用”策略。如果数组顺序变了(比如在开头插入一个元素),React 会发现每个位置的元素都变了,从而导致大量的卸载与重新创建,性能极差。
  • 有 Key 的情况:Key 就像是每个节点的“身份证”。React 能够通过 Key 准确找到旧集合中对应的节点。如果 Key 相同,React 只需移动位置(Move),而不需要重新创建。

3. 为什么不建议用 Index 作为 Key?

这是一个非常经典的考点:

  • 场景回溯:如果你在列表头部插入一条数据,所有数据的 Index 都会发生偏移。
  • 后果:React 比较发现 Key 没变,但内容变了,于是会重新渲染。更严重的是,如果列表包含非受控组件(如 <input>),会导致状态错乱。
  • 最佳实践:始终使用数据中的唯一标识符(如 ID)

4. 深度追问:React 16 之后的 Fiber 架构对 Diff 有影响吗?

有。

在 React 15 及之前,Diff 是递归进行的(Stack Reconciler),一旦开始就无法中断。

在 React 16+(Fiber Reconciler)中,Diff 过程变成了可中断的循环。React 会利用浏览器的空闲时间进行对比,如果此时有高优先级的任务(如用户输入),对比会暂停,从而解决了大组件更新时的卡顿问题。


🌟 总结

“React 的 Diff 算法通过同层比较类型判断Key 值复用,将 O(n³) 的复杂度优化到了 O(n)。而 Key 的存在,是为了在动态列表中帮助 React 识别节点身份,从而实现最小化的 DOM 操作。”