useEffect实现
On this page
在架构篇commit阶段流程概览我们讲解了useEffect
的工作流程。
其中我们谈到
在
flushPassiveEffects
方法内部会从全局变量rootWithPendingPassiveEffects
获取effectList
。
本节我们深入flushPassiveEffects
方法内部探索useEffect
的工作原理。
flushPassiveEffectsImpl
flushPassiveEffects
内部会设置优先级
,并执行flushPassiveEffectsImpl
。
你可以从这里看到
flushPassiveEffects
的代码
flushPassiveEffectsImpl
主要做三件事:
调用该
useEffect
在上一次render
时的销毁函数调用该
useEffect
在本次render
时的回调函数如果存在同步任务,不需要等待下次
事件循环
的宏任务
,提前执行他
本节我们关注前两步。
在v16
中第一步是同步执行的,在官方博客中提到:
副作用清理函数(如果存在)在 React 16 中同步运行。我们发现,对于大型应用程序来说,这不是理想选择,因为同步会减缓屏幕的过渡(例如,切换标签)。
基于这个原因,在v17.0.0
中,useEffect
的两个阶段会在页面渲染后(layout
阶段后)异步执行。
事实上,从代码中看,
v16.13.1
中已经是异步执行了
接下来我们详细讲解这两个步骤。
阶段一:销毁函数的执行
useEffect
的执行需要保证所有组件useEffect
的销毁函数
必须都执行完后才能执行任意一个组件的useEffect
的回调函数
。
这是因为多个组件
间可能共用同一个ref
。
如果不是按照“全部销毁”再“全部执行”的顺序,那么在某个组件useEffect
的销毁函数
中修改的ref.current
可能影响另一个组件useEffect
的回调函数
中的同一个ref
的current
属性。
在useLayoutEffect
中也有同样的问题,所以他们都遵循“全部销毁”再“全部执行”的顺序。
在阶段一,会遍历并执行所有useEffect
的销毁函数
。
// pendingPassiveHookEffectsUnmount中保存了所有需要执行销毁的useEffect
const unmountEffects = pendingPassiveHookEffectsUnmount;
pendingPassiveHookEffectsUnmount = [];
for (let i = 0; i < unmountEffects.length; i += 2) {
const effect = ((unmountEffects[i]: any): HookEffect);
const fiber = ((unmountEffects[i + 1]: any): Fiber);
const destroy = effect.destroy;
effect.destroy = undefined;
if (typeof destroy === 'function') {
// 销毁函数存在则执行
try {
destroy();
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}
}
其中pendingPassiveHookEffectsUnmount
数组的索引i
保存需要销毁的effect
,i+1
保存该effect
对应的fiber
。
向pendingPassiveHookEffectsUnmount
数组内push
数据的操作发生在layout阶段
commitLayoutEffectOnFiber
方法内部的schedulePassiveEffects
方法中。
commitLayoutEffectOnFiber
方法我们在Layout阶段已经介绍
function schedulePassiveEffects(finishedWork: Fiber) {
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 {
const {next, tag} = effect;
if (
(tag & HookPassive) !== NoHookEffect &&
(tag & HookHasEffect) !== NoHookEffect
) {
// 向`pendingPassiveHookEffectsUnmount`数组内`push`要销毁的effect
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
// 向`pendingPassiveHookEffectsMount`数组内`push`要执行回调的effect
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}
阶段二:回调函数的执行
与阶段一类似,同样遍历数组,执行对应effect
的回调函数
。
其中向pendingPassiveHookEffectsMount
中push
数据的操作同样发生在schedulePassiveEffects
中。
// pendingPassiveHookEffectsMount中保存了所有需要执行回调的useEffect
const mountEffects = pendingPassiveHookEffectsMount;
pendingPassiveHookEffectsMount = [];
for (let i = 0; i < mountEffects.length; i += 2) {
const effect = ((mountEffects[i]: any): HookEffect);
const fiber = ((mountEffects[i + 1]: any): Fiber);
try {
const create = effect.create;
effect.destroy = create();
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}