本文将带大家实现 useLayoutEffect 与 useEffect。
先看下如何使用。
js
function FunctionComponent() {
const [count1, setCount1] = useReducer((x) => x + 1, 0)
const [count2, setCount2] = useState(0)
useLayoutEffect(() => {
console.log('useLayoutEffect')
}, [count1])
useEffect(() => {
console.log('useEffect')
}, [count2])
return (
<div className="border">
<h1>函数组件</h1>
<button onClick={() => setCount1()}>{count1}</button>
<button onClick={() => setCount2(count2 + 1)}>{count2}</button>
</div>
)
}
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
(<FunctionComponent />) as any
);
两者对比
存储结构相同
存储结构都是单向循环链表。
执行时机不同
- useLayoutEffect 会在所有的 DOM 变更之后同步调用 effect。
- useEffect 会在组件渲染到屏幕之后延迟执行 effect。
useLayoutEffect 可能会影响性能,尽可能使用 useEffect。比如在 useLayoutEffect 增加 ajax 请求,由于是同步执行,可能会影响 DOM 的渲染。
实现 useLayoutEffect 与 useEffect
js
function pushEffect(
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<any> | void | null
) {
const effect: Effect = {
tag: hookFlags,
create,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber!.updateQueue;
// 单向循环链表
if (componentUpdateQueue === null) {
// 第一个 effect
componentUpdateQueue = {
lastEffect: null,
};
currentlyRenderingFiber!.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
return effect;
}
// 存储 effect
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<any> | void | null
) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 依赖项是否发生变化
if (currentHook !== null) {
if (nextDeps !== null) {
const prevDeps = currentHook.memoizedState.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
return;
}
}
}
currentlyRenderingFiber!.flags |= fiberFlags;
// * 1. 保存 effect 2. 构建 effect 链表
hook.memoizedState = pushEffect(hookFlags, create, nextDeps);
}
// useEffect 与 useLayoutEffect 的区别
// 存储结构一样
// effect 和 destroy 函数的执行时机不同
export function useLayoutEffect(
create: () => (() => void) | void,
deps: Array<any> | void | null
) {
return updateEffectImpl(Update, HookLayout, create, deps);
}
export function useEffect(
create: () => (() => void) | void,
deps: Array<any> | void | null
) {
return updateEffectImpl(Passive, HookPassive, create, deps);
}
Commit 阶段
commitReconciliationEffects
增加更新的 case。
js
function commitHookEffectListMount(hookFlags: HookFlags, finishedWork: Fiber) {
const updateQueue = finishedWork.updateQueue;
let lastEffect = updateQueue!.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & hookFlags) === hookFlags) {
const create = effect.create;
create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
function commitReconciliationEffects(finishedWork: Fiber) {
const flags = finishedWork.flags;
if (flags & Placement) {
// 页面初次渲染 新增插入 appendChild
// todo 页面更新,修改位置 appendChild || insertBefore
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
// 删除
if (flags & ChildDeletion) {
// parentFiber 是 deletions 的父 dom 对应的 fiber
const parentFiber = isHostParent(finishedWork)
? finishedWork
: getHostParentFiber(finishedWork);
const parentDOM = parentFiber.stateNode;
commitDeletions(finishedWork.deletions!, parentDOM);
finishedWork.flags &= ~ChildDeletion;
finishedWork.deletions = null;
}
// 更新
if (flags & Update) {
if (finishedWork.tag === FunctionComponent) {
// 执行 layout effect
commitHookEffectListMount(HookLayout, finishedWork);
finishedWork.flags &= ~Update;
}
}
}
commitRoot
增加 passive effect阶段,执行 passive effect。
js
function commitPassiveEffects(finishedWork: Fiber) {
switch (finishedWork.tag) {
case FunctionComponent: {
if (finishedWork.flags & Passive) {
commitHookEffectListMount(HookPassive, finishedWork);
finishedWork.flags &= ~Passive;
}
break;
}
}
}
function recursivelyTraversePassiveMountEffects(finishedWork: Fiber) {
let child = finishedWork.child;
while (child !== null) {
// 1. 遍历子节点,检查子节点
recursivelyTraversePassiveMountEffects(child);
// 2. 如果有 passive effects,执行~
commitPassiveEffects(finishedWork);
child = child.sibling;
}
}
export function flushPassiveEffects(finishedWork: Fiber) {
// 1. 遍历子节点,检查子节点
recursivelyTraversePassiveMountEffects(finishedWork);
// 2. 如果有 passive effects,执行~
commitPassiveEffects(finishedWork);
}
function commitRoot(root: FiberRoot) {
// 1. commit 阶段开始
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
// 2.1 mutation阶段, 渲染DOM树
commitMutationEffects(root, root.finishedWork as Fiber); //Fiber,HostRoot=3
// 2.2 passive effect阶段,执行 passive effect
Scheduler.scheduleCallback(NormalPriority, () => {
flushPassiveEffects(root.finishedWork as Fiber);
});
// 3. commit 结束
executionContext = prevExecutionContext;
workInProgressRoot = null;
}