本文对应的 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 源码文章
本文详细解析了React组件卸载时的内存释放机制,包括detachFiberAfterEffects和detachAlternateSiblings两个阶段,以及根节点的特殊处理。在组件卸载过程中,从根组件开始遍历,执行useEffect返回的函数,释放相关DOM和内存资源。同时,介绍了React组件树的遍历技巧和useEffect执行顺序。
1599

被折叠的 条评论
为什么被折叠?



