Skip to content

React 的 diff 算法

React 中 Diff 算法的本质是对比,current fiberJSX对象, 然后生成workInProgress fiber

React diff 的限制

  • 只对同级元素进行 Diff。如果一个 DOM 节点在前后两次更新中跨越了层级,那么 React不会尝试复用他。
  • 两个不同类型的元素会产生出不同的树。如果元素由div变为p,React 会销毁div 及其子孙节点,并新建 p 及其子孙节点。
  • 开发者可以通过 key prop 来暗示哪些子元素在不同的渲染下能保持稳定

单节点的 diff

mount 时

current fiber为 Null

直接根据jsx element创建workInProgress fiber

update 时

current fiber存在

遍历 current fiber 和兄弟节点

  • current fiberjsx element的 key相同

    • 两者的节点 type相同
      • 当前层级只有一个 fiber, 需要删除其他多余的兄弟 fiber
      • useFiber 复用current fiber, 返回workInProgress fiber
    • 两者的节点 type不相同
      • 标记删除该current fiber, 以及它所有的兄弟节点
  • current fiberjsx element的 key不相同 只标记删除该current fiber

  • 如果以上对比后,还是没法复用,就使用jsx element创建新的workInProgress fiber

多节点的 diff

Diff 算法的整体逻辑会经历两轮遍历:

第一轮遍历:处理更新的节点。

第二轮遍历:处理剩下的不属于更新的节点

情况

:::: tabs @tab 节点更新

html
// 之前
<ul>
  <li key="0" className="before">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后 情况1 —— 节点属性变化
<ul>
  <li key="0" className="after">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后 情况2 —— 节点类型更新
<ul>
  <div key="0">0</div>
  <li key="1">1</li>
  <li></li>
</ul>
// 之前
<ul>
  <li key="0" className="before">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后 情况1 —— 节点属性变化
<ul>
  <li key="0" className="after">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后 情况2 —— 节点类型更新
<ul>
  <div key="0">0</div>
  <li key="1">1</li>
  <li></li>
</ul>

@tab 节点新增或减少

html
// 之前
<ul>
  <li key="0">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后 情况1 —— 新增节点
<ul>
  <li key="0">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
  <li key="2">2</li>
  <li></li>
</ul>

// 之后 情况2 —— 删除节点
<ul>
  <li key="1">1</li>
  <li></li>
</ul>
// 之前
<ul>
  <li key="0">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后 情况1 —— 新增节点
<ul>
  <li key="0">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
  <li key="2">2</li>
  <li></li>
</ul>

// 之后 情况2 —— 删除节点
<ul>
  <li key="1">1</li>
  <li></li>
</ul>

@tab 节点位置变化

html
<ul>
  <li key="0">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后
<ul>
  <li key="1">1</li>
  <li></li>
  <li key="0">0</li>
  <li></li>
</ul>
<ul>
  <li key="0">0</li>
  <li></li>
  <li key="1">1</li>
  <li></li>
</ul>

// 之后
<ul>
  <li key="1">1</li>
  <li></li>
  <li key="0">0</li>
  <li></li>
</ul>

::::

第一轮遍历