认识
一、认识
useLayoutEffect
初始渲染或者状态更新之后, 在 commit
阶段中同步执行, 同步执行时阻塞浏览器渲染。useEffect
初始渲染或者状态更新之后, 在 commit
阶段完成以后异步执行,防止同步执行时阻塞浏览器渲染。对于不同的 effect
钩子, 父子组件的执行顺序是: 先子后父。
二、细节
2.1 effect
const effect: Effect = {
tag,
create,
inst,
deps,
next: (null: any), // 环形链表
};
effect
对象中有 create
回调函数、deps
依赖项、存储 effect
专属的 fiber flag
、以及 next
组成的环形链表
2.1 fiber.updateQueue
fiber.updateQueue
存储 useEffect
的 effect
副作用链表
const fiber = currentlyRenderingFiber;
const updateQueue = fiber.updateQueue;
if (updateQueue == null) {
const updateQueue = createFCUpdateQueue();
fiber.updateQueue = updateQueue;
effect.next = effect;
updateQueue.lastEffect = effect;
} else {
const lastEffect = updateQueue.lastEffect;
if (lastEffect == null) {
effect.next = effect;
updateQueue.lastEffect = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
updateQueue.lastEffect = effect;
}
}
2.2 hook.memoizesState
hook.memoizesState
存储 useEffect
的 effect
副作用链表
hook.memoizedState = pushEffect(
Passive | HookHasEffect,
create,
undefined,
nextDeps
);
2.3 fiber.memoizedState
fiber.memoizedState
存储 Hooks
链表
if (workInProgressHook == null) {
if (currentlyRenderingFiber == null) {
throw new Error('请在函数组件内调用 Hook');
} else {
workInProgressHook = hook;
currentlyRenderingFiber.memoizedState = workInProgressHook;
}
} else {
workInProgressHook.next = hook;
workInProgressHook = hook;
}
三、执行函数组件
在 React.js
的 beginWork
阶段, 遇到 FunctionComponent
类型的 Fiber
, 会执行 updateFunctionComponent
。updateFunctionComponent
内部调用 renderWithHooks
。主要工作如下:
-
重置状态: 函数组件执行前, 重置当前正在处理的
hook
、当前正在使用的hook
-
记录当前组件
fiber
: 通过currentlyRenderingFiber
变量记录当前组件fiber
-
选择对应阶段的
Hook
:mount
阶段选择HooksDispatcherOnMount
作为Hooks Map
,update
阶段选择HooksDispatcherOnUpdate
作为Hooks Map
。 函数组件mount
阶段 创建hook
数据结构, 存储到fiber.memoizedState
或者hook.next
, 初次建立其hooks
与fiber
之间的关系。函数组件update
阶段 找到当前函数组件fiber
, 找到当前hook
, 更新hook
状态。 -
执行函数组件: 执行我们真正函数组件,所有的
hooks
将依次执行, 每个hook
内部要根据currentlyRenderingFiber
读取对应的内容, -
重置状态: 执行完
Component
, 立马重置currentlyRenderingFiber
, 防止函数组件外部调用。置当前正在处理的hook
、当前正在使用的hook
3.1 /packages/react-reconciler/fiberHooks.js
export function renderWithHooks(workInProgress, Component, lane) {
renderLane = lane;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
const current = workInProgress.alternate;
if (current !== null) {
// update
currentDispatcher.current = HooksDispatcherOnUpdate;
} else {
// mount
currentDispatcher.current = HooksDispatcherOnMount;
}
const props = workInProgress.pendingProps;
const children = Component(props);
currentHook = null;
renderLane = NoLane;
workInProgressHook = null;
currentlyRenderingFiber = null;
return children;
}
3.2 /packages/react-reconciler/fiberHooks.js
/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
use,
useRef: mountRef,
useMemo: mountMemo,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useCallback: mountCallback,
useTransition: mountTransition
};
/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
use,
useRef: updateRef,
useMemo: updateMemo,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useCallback: updateCallback,
useTransition: updateTransition
};
四、函数组件 mount 阶段 Hook
五、函数组件 update 阶段 Hook
六、沉淀与思考
useEffect
在 commit
阶段完成以后 (真实 DOM
挂载完成以后) 通过 scheduleCallback
异步调度执行, 不会阻塞浏览器渲染。而 useLayoutEffect
在 commit
阶段 (先于 DOM
挂载) 同步执行, 会阻塞浏览器渲染。伪代码如下所示:
function commitRoot(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
spawnedLane: Lane,
) {
try {
commitRootImpl(
root,
recoverableErrors,
transitions,
previousUpdateLanePriority,
spawnedLane,
);
} finally {
}
return null;
}
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
renderPriorityLevel: EventPriority,
spawnedLane: Lane,
) {
do {
// commit 阶段首先执行 useLayoutEffect
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
……
}
if(){
……
}else {
……
}
root.finishedWork = null;
root.finishedLanes = NoLanes;
if (finishedWork === root.current) {
……
}
markRootFinished(root, remainingLanes, spawnedLane);
if (root === workInProgressRoot) {
……
} else {
……
}
// useEffect 通过 scheduleCallback 异步调度执行
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
pendingPassiveTransitions = transitions;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
ReactCurrentOwner.current = null;
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
if (enableProfilerTimer) {
recordCommitTime();
}
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
rootCommittingMutationOrLayoutEffects = root;
}
// DOM 挂载、卸载、useEffect 副作用收集 操作
commitMutationEffects(root, finishedWork, lanes);
}