掌握 React 元件樹遍歷技巧

uccs發表於2023-04-14

本文對應的 react 版本是 18.2.0

下面的 dom 結構react 內部是如何遍歷的

const App = () => {
  return (
    <div>
      <button>+1</button>
      <A count={0} />
    </div>
  );
};
const A = (props) => {
  useEffect(() => {
    console.log(props.count);
  }, [props.count]);
  return <div>{props.count}</div>;
};

react 內部遍歷核心邏輯:

  1. render 時呼叫 commitPassiveUnmountOnFiber 函式
  2. commitPassiveUnmountOnFiber 處理不同的 WorkTag,並呼叫 recursivelyTraversePassiveUnmountEffects
  3. recursivelyTraversePassiveUnmountEffects 根據當前 Fiber 的子節點有沒有 passive effectuseEffectuseLayoutEffect)來決定是否遍歷當前 Fiber 的子節點

    • 如果子節點有 passive effect,則優先遍歷子節點 (深度優先),直到找到最終的葉子節點,退出當前迴圈
    • 然後進入兄弟節點,開始遍歷兄弟節點的子節點

      • 具體從哪個兄弟節點開始遍歷,react 選擇的是離退出迴圈的那個葉子節點的父節點,檢查有沒有子節點,以此迴圈遍歷
    • 直到最後找到所有有 passive effect 的節點

程式碼簡化:

commitPassiveUnmountOnFiber(root.current);

function commitPassiveUnmountOnFiber(finishedWork) {
  // 省略了處理不同的 WorkTag
  recursivelyTraversePassiveUnmountEffects(finishedWork);
}

function recursivelyTraversePassiveUnmountEffects(parentFiber) {
  // 省略了其他處理
  if (parentFiber.subtreeFlags & PassiveMask) {
    let child = parentFiber.child;
    while (child !== null) {
      commitPassiveUnmountOnFiber(child);
      child = child.sibling;
    }
  }
}

所以對於這段 dom 的遍歷邏輯是:

  1. 首先從根元件開始 FiberRootNode,取到 current

    • 也就是說 FiberRootNode.currentdiv#root 這是一個 fiber,它的 tag3
  2. 由於 App 的子元件有 passive effect,所以會進入 App 元件,它的 tag0
  3. App 元件中節點是 <div><di >tag5

    • <div> 下面有兩個子元素 <button><A>
  4. 先遍歷 <button> 它的 tag5
  5. <button> 內部只有一個文字節點,沒有 passive effect

    • 所以 react 不遍歷了(跳出當前遍歷的迴圈,也就是 button 這條不在遍歷了)
  6. 跳出迴圈後,檢視 button 的兄弟節點,它的兄弟節點是 <A><A>tag0
  7. 由於 <A> 節點的子節點沒有 passive effect,所以跳出迴圈,結束整個遍歷

總結

  1. 從跟節點開始遍歷
  2. 當前元件的子元件有沒有 passive effect
  3. 採取深度優先
  4. 如果 dom 節點內有函式元件,則這個 dom 會被遍歷,否則不會遍歷
  5. 如果當前 fiber 下的所有子 fiber 都沒有 passive effect ,則這一整個都連結串列都不會被遍歷
  6. 如果當前 fiber 只有 dom,則這些 dom 也不會遍歷

總的來說元件會不會別遍歷看 fiber 有沒有 passive effect

  • 有,一定會被遍歷
  • 沒有,下面兩種情況會被遍歷,其他情況不會被遍歷

    • passive effect 的父元件
    • passive effect 元件是兄弟元件

passive effect 指的是 useEffectuseLayoutEffect

遍歷邏輯如下圖所示

圖中畫綠色勾的都會被遍歷,紅色勾是遍歷的順序

react遍歷邏輯

往期文章

  1. 深入探究 React 原生事件的工作原理
  2. React Lane 演算法:一文詳解 8 種 Lane 操作
  3. 剖析 React 任務排程機制:scheduleCallback 實現原理

更多 react 原始碼文章

相關文章