この記事の対応react
バージョンは18.2.0
正式に開始する前に、 afiber
の。deletions
この属性は削除されたものを現在のノードに保存しfiber
、この配列はcommit
ステージ
削除されたノードがある場合、プロパティ値は配列であり、削除されたノードがない場合、プロパティ値はnull
const A = () => {
useEffect(() => {
return () => {
console.log("A unmount");
};
}, []);
return <div>文本A</div>;
};
const B = () => {
useEffect(() => {
return () => {
console.log("B unmount");
};
}, []);
return <div>文本B</div>;
};
复制代码
App
コンポーネントがこのように書かれている場合、 のdeletions
値は[FiberNode, FiberNode]
const App(){
const [count, setCount] = useState(0)
return <div>
{count % 2 === 0 && <A />}
{count % 2 === 0 && <B />}
<div onClick={()=> setCount(count+1)}>+1</div>
</div>
}
复制代码
App
コンポーネントがこのように書かれている場合、 のdeletions
値は[FiberNode]
const App(){
const [count, setCount] = useState(0)
return <div>
{count % 2 === 0 && <><A /><B /></>}
<div onClick={()=> setCount(count+1)}>+1</div>
</div>
}
复制代码
2 番目のケースでは、コンポーネントreact
とコンポーネントが全体として考慮されるため、の値はA
B
deletions
[FiberNode]
現在のノードの削除を処理する
react
トラバースするfiber tree
ときは、現在のfiber
ものを最初に処理しdeletions
、処理後に次のものをトラバースします。fiber
これで、保存deletions
されているのは、現在のfiber
この時点で、配列をトラバースreact
し、deletions
それぞれfiber
によってpassive effect
返された関数を実行します。
しかし、問題がありますdeletions
。fiber
子ノードがある場合、これらの子ノードも削除されます。現時点でどのように対処すれreact
ば?
ここでは、次の 2 つの状況について説明します。
- 削除済みには子が
fiber
ありません:<div>{xxxx && <A />}</div>
- 削除されたノードには子ノード
fiber
があります:<div>{xxxx && <><A /><B /></>}</div>
-->
削除されたファイバーには子がありません。<div>{xxxx && <A />}</div>
この状況がよくわかる
当遍历到 div
时,因为 <A/>
节点会被卸载,所以在 div
的 deletions
保存了一个 <A/>
的 fiber
遍历 deletions
数组,执行 <A/>
的 passive effect
返回的函数
如下图所示:
删除的 fiber 有子节点:<div>{xxxx && <><A /><B /></>}</div>
这种情况就比较复杂了
当遍历到 div
时,<></>
节点会被卸载,所以在 div
的 deletions
保存了一个 <></>
的 fiber
遍历 deletions
数组,执行 fiber
的 passive effect
返回的函数,对于 <></>
来说是不存在的 passive effect
那么这个时候就要去遍历它的 child.fiber
,也就是 <A/>
和 <B/>
首先拿到第一个 fiber,也就是 <A/>
,然后执行 <A/>
的 passive effect
返回的函数,这步比较好理解
child = fiber.child;
if (child !== null) {
nextEffect = child;
}
复制代码
这里遍历也是深度优先,遍历一个 child
,执行一个 passive effect
返回函数,然后再遍历下一个 child
(这边 <A />
已经是叶子节点了)
然后拿到第二个 fiber
,也就是 <B/>
,然后执行 <B/>
的 passive effect
返回的函数,这步就不太好理解了
child = fiber.child;
if (child !== null) {
nextEffect = child;
} else {
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot);
}
复制代码
这里要注意的是:
react
在寻找有 passive effect
的 fiber
时,只遍历到有 passive effect
的 fiber
, 像 div
这种没有 passive effect
就不会遍历
但是在处理 deletions
,react
会遍历所有的 fiber
,也就是说从当前的 fiber
开始,一直往下遍历到叶子节点,这个叶子节点是指文本节点这种,往下不会有节点了(对于 A
组件来说 文本A
是文本节点)
然后在开始往上遍历,往上遍历是调用 commitPassiveUnmountEffectsInsideOfDeletedTree_complete
函数,直到遍历到 deletionRoot
,在向上遍历的过程中会检查是否有 sibling
,如果有说明 sibling
还没被处理,这样就找到了 <B/>
,然后执行 <B/>
的 passive effect
返回的函数
如下图所示:
向下遍历和向上遍历
在处理 deletions
时,对于每个 deletedNode
,都先向下遍历,然后再向上遍历
- 向下遍历:
commitPassiveUnmountEffectsInsideOfDeletedTree_begin
(深度优先,优先处理左边的节点) - 向上遍历:
commitPassiveUnmountEffectsInsideOfDeletedTree_complete
(之后再处理右边节点)
总结
1. 遍历 deletions 数组:
react
在处理deletions
时,先沿着fiber tree
向下遍历,如果有passive effect
返回的函数,则执行- 一直遍历到没有
child
的fiber
,再向上遍历,处理sibling
- 再向上遍历时,如果如果遇到
sibling
,再向下遍历,向下遍历时遇到passive effect
返回的函数,则执行 - 如此循环直到遍历到
deletedNode
,结束遍历
2. 结合掌握 React 组件树遍历技巧
- 遍历寻找有
passive effect
节点react
从根组件向下遍历,如果没有passive effect
,则不会遍历
- 遍历时,如果遇到当前节点有
deletions
时,会暂停寻找passive effect
节点- 进入遍历
deletions
数组
- 进入遍历
react 遍历 deletions 完整逻辑如下图所示:
图中绿色部分是遍历 deletionsNode
过程,红色部分是遍历寻找 passive effect
过程
往期文章
- 深入探究 React 原生事件的工作原理
- React Lane 算法:一文详解 8 种 Lane 操作
- 剖析 React 任务调度机制:scheduleCallback 实现原理
- 掌握 React 组件树遍历技巧
更多 react
源码文章