本文对应的 react
版本是 18.2.0
通过上两讲:
- 把握 React 组件树遍历技巧
- useEffect 返回的函数是怎么执行的
咱们曾经晓得了 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
源码文章