本文对应的 react
版本是 18.2.0
通过上两讲:
我们已经知道了 react
是如何找到 passive effect
返回的函数
那么找到这个函数后,怎么执行这个函数呢
我们先来看下面这段代码:
function A() {
useEffect(() => {
return () => {
console.log("执行销毁函数 A");
};
}, []);
useEffect(() => {
return () => {
console.log("执行销毁函数 A1");
};
}, []);
return <>文本A</>;
}
一个组件中有两个 passive effect
返回的函数,react
是怎么安排执行的顺序呢?
一个组件中的 passive effect
是用链表的形式存储的
每个 effect
对象都有 destroy
和 next
属性
destroy
保存的是passive effect
返回的函数next
保存的是下一个effect
对象
最顶层的 effect
是函数组件中写在最上面的 useEffect
,通过 next
指向下一个 effect
,以此类推,最后一个 effect
的 next
指向最顶层的 effect
结构如下所示:
let effect = {
destroy: () => {
console.log("执行销毁函数 A"));
},
next: {
destroy: () => {
console.log("执行销毁函数 A1");
},
next: {
destroy: () => {
console.log("执行销毁函数 A");
},
next: { ... },
},
},
};
既然是链表,那么执行的顺序就是从最顶层的 effect
开始,依次执行 destroy
函数,最后执行最顶层的 effect
的 destroy
函数
function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null
) {
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
react
这里使用 do...while
进行遍历,保证所有的 effect
都被执行
释放内存
释放内存分为两个阶段:
- 第一个阶段是在向上遍历时
- 第二个阶段是在处理完成
deletions
时
detachFiberAfterEffects
上回说到 react
在处理 deletedNode
时先向下遍历,然后在向上遍历
在向上遍历的过程中会将对应所有遍历到的 fiber
的属性都置为 null
,这样可以释放一些内存
function detachFiberAfterEffects(fiber) {
const alternate = fiber.alternate;
if (alternate !== null) {
fiber.alternate = null;
detachFiberAfterEffects(alternate);
}
fiber.child = null;
fiber.deletions = null;
fiber.sibling = null;
if (fiber.tag === HostComponent) {
const hostInstance = fiber.stateNode;
if (hostInstance !== null) {
delete hostInstance[internalInstanceKey];
delete hostInstance[internalPropsKey];
delete hostInstance[internalEventHandlersKey];
delete hostInstance[internalEventHandlerListenersKey];
delete hostInstance[internalEventHandlesSetKey];
}
}
fiber.stateNode = null;
fiber.return = null;
fiber.dependencies = null;
fiber.memoizedProps = null;
fiber.memoizedState = null;
fiber.pendingProps = null;
fiber.stateNode = null;
fiber.updateQueue = null;
}
detachAlternateSiblings
当处理完 deletions
时,当前 fiber
的 alternate
及 alternate
下所有的子节点也会被置为 null
,这样可以释放一些内存
function detachAlternateSiblings(parentFiber) {
const previousFiber = parentFiber.alternate;
if (previousFiber !== null) {
let detachedChild = previousFiber.child;
if (detachedChild !== null) {
previousFiber.child = null;
do {
const detachedSibling = detachedChild.sibling;
detachedChild.sibling = null;
detachedChild = detachedSibling;
} while (detachedChild !== null);
}
}
}
根节点处理
react
每次遍历都是从根节点开始,那么根节点的处理是怎么样的呢?
在这里 掌握 React 组件树遍历技巧 我们知道 react
是通过调用 commitPassiveUnmountOnFiber
函数来寻找有 passive effect
的 fiber
按照源码去追踪,我们会发现在 recursivelyTraversePassiveUnmountEffects
函数中会调用 commitHookPassiveUnmountEffects
函数,具体解释可以查这里:commitPassiveUnmountOnFiber
function commitPassiveUnmountOnFiber(finishedWork, type) {
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (finishedWork.flags & Passive) {
commitHookPassiveUnmountEffects(
finishedWork,
finishedWork.return,
HookPassive | HookHasEffect
);
}
}
react
为什么要多此一举呢?
通过不断的打断点会看到,commitHookPassiveUnmountEffects
函数会被调用两次
recursivelyTraversePassiveUnmountEffects
函数处理的是 finishedWork.chile
,而 commitHookPassiveUnmountEffects
函数处理的是 finishedWork
因为 react
是从根节点开始遍历的,所以 commitHookPassiveUnmountEffects
只处理根节点的 passive effect
的返回函数
总结
react
从根组件开始遍历,寻找passive effect
的fiber
在遍历时,会检查每个
fiber
的deletions
- 如果有则暂停
passive effect
的遍历,先处理deletions
- 处理完
deletions
后,再继续遍历passive effect
的fiber
- 如果有则暂停
在处理
deletions
时,会先向下遍历,然后再向上遍历- 向下遍历时,执行
passive effect
的返回函数 向上遍历时
- 如果遇到
sibling
,则会沿着sibling
向下遍历 - 将
fiber
的所有属性置为null
,释放内存 - 直到遇到
deletedNode
结束处理deletions
- 如果遇到
- 向下遍历时,执行
- 根节点的
passive effect
返回的函数会单独处理
往期文章
- 深入探究 React 原生事件的工作原理
- React Lane 算法:一文详解 8 种 Lane 操作
- 剖析 React 任务调度机制:scheduleCallback 实现原理
- 掌握 React 组件树遍历技巧
- useEffect 返回的函数是怎么执行的
更多 react
源码文章
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。