Open jtwang7 opened 2 years ago
参考文章:React技术揭秘
// start performSyncWorkOnRoot() || performConcurrentWorkOnRoot() => fiberRootNode // --- render --- // --- commit --- commitRoot(fiberRootNode) - before mutation // 执行DOM操作前 - commitBeforeMutationEffects() - mutation // 执行DOM操作 - commitMutationEffects() // 根据effectTag分别调用不同的方法处理 - Placement effect - commitPlacement() - Update effect - FunctionComponent mutation & commitHookEffectListUnmount() // 遍历effectList,执行所有useLayoutEffect hook的销毁函数 - HostComponent mutation & commitUpdate() // 将render阶段中为Fiber节点赋值的updateQueue对应内容渲染到页面 - Deletion effect - commitDeletion() - layout // 执行DOM操作后
render阶段
performSyncWorkOnRoot() || performConcurrentWorkOnRoot()
performUnitOfWork()
beginWork()
reconcileChildren()
mountChildFibers()
reconcileChildFibers()
completeWork()
commit阶段
commitRoot(fiberRootNode)
before mutation
遍历effectList并调用commitBeforeMutationEffects函数处理。
effectList
commitBeforeMutationEffects
// 保存之前的优先级,以同步优先级执行,执行完毕后恢复之前优先级 const previousLanePriority = getCurrentUpdateLanePriority(); setCurrentUpdateLanePriority(SyncLanePriority); // 将当前上下文标记为CommitContext,作为commit阶段的标志 const prevExecutionContext = executionContext; executionContext |= CommitContext; // 处理focus状态 focusedInstanceHandle = prepareForCommit(root.containerInfo); shouldFireAfterActiveInstanceBlur = false; // beforeMutation阶段的主函数 commitBeforeMutationEffects(finishedWork); focusedInstanceHandle = null;
commitBeforeMutationEffects()
整体可以分为三部分:
DOM节点
autoFocus
blur
getSnapshotBeforeUpdate
useEffect
整个useEffect异步调用分为三步: before mutation阶段在scheduleCallback中调度flushPassiveEffects flushPassiveEffects方法内部执行流程: 从全局变量rootWithPendingPassiveEffects获取effectList(Placement|Update|Deletion|effectTag),然后遍历rootWithPendingPassiveEffects(即effectList)执行effect回调函数。 layout阶段之后将effectList赋值给rootWithPendingPassiveEffects scheduleCallback触发flushPassiveEffects,flushPassiveEffects内部遍历rootWithPendingPassiveEffects
整个useEffect异步调用分为三步:
before mutation阶段在scheduleCallback中调度flushPassiveEffects
before mutation阶段
scheduleCallback
flushPassiveEffects
flushPassiveEffects方法内部执行流程: 从全局变量rootWithPendingPassiveEffects获取effectList(Placement|Update|Deletion|effectTag),然后遍历rootWithPendingPassiveEffects(即effectList)执行effect回调函数。
flushPassiveEffects方法内部执行流程:
从全局变量rootWithPendingPassiveEffects获取effectList(Placement|Update|Deletion|effectTag),然后遍历rootWithPendingPassiveEffects(即effectList)执行effect回调函数。
rootWithPendingPassiveEffects
effectList(Placement|Update|Deletion|effectTag)
effect
layout阶段之后将effectList赋值给rootWithPendingPassiveEffects
layout阶段
scheduleCallback触发flushPassiveEffects,flushPassiveEffects内部遍历rootWithPendingPassiveEffects
function commitBeforeMutationEffects() { while (nextEffect !== null) { const current = nextEffect.alternate; if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) { // ...focus blur相关 } const effectTag = nextEffect.effectTag; // 调用getSnapshotBeforeUpdate if ((effectTag & Snapshot) !== NoEffect) { commitBeforeMutationEffectOnFiber(current, nextEffect); } // 调度useEffect if ((effectTag & Passive) !== NoEffect) { if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; // scheduleCallback()由Scheduler模块提供:以某个优先级异步调度一个回调函数 // 此处flushPassiveEffects()将以normal优先级被异步调用 scheduleCallback(NormalSchedulerPriority, () => { // flush副作用队列:此处触发useEffect回调 flushPassiveEffects(); return null; }); } } nextEffect = nextEffect.nextEffect; } }
关于getSnapshotBeforeUpdate(): 生命周期钩子getSnapshotBeforeUpdate()实际是替代React v15中render阶段的UNSAFE_componentWillxxx()生命周期函数的解决方案。 React v16开始,Stack Reconciler重构为Fiber Reconciler后,render阶段的任务可能中断/重新开始,对应的组件在render阶段的生命周期钩子(即componentWillxxx())可能触发多次。为了解决该问题,React v16 在 commit 的 before mutation 阶段内重新实现了类似功能的getSnapshotBeforeUpdate(),由于commit阶段是同步且无法中断的,因此避免了多次调用的情况。
关于getSnapshotBeforeUpdate():
getSnapshotBeforeUpdate()
生命周期钩子getSnapshotBeforeUpdate()实际是替代React v15中render阶段的UNSAFE_componentWillxxx()生命周期函数的解决方案。
UNSAFE_componentWillxxx()
React v16开始,Stack Reconciler重构为Fiber Reconciler后,render阶段的任务可能中断/重新开始,对应的组件在render阶段的生命周期钩子(即componentWillxxx())可能触发多次。为了解决该问题,React v16 在 commit 的 before mutation 阶段内重新实现了类似功能的getSnapshotBeforeUpdate(),由于commit阶段是同步且无法中断的,因此避免了多次调用的情况。
Stack Reconciler
Fiber Reconciler
componentWillxxx()
mutation
遍历effectList并调用commitMutationEffects函数处理。
commitMutationEffects
nextEffect = firstEffect; // 循环遍历effectList do { try { // 调用commitMutationEffects() commitMutationEffects(root, renderPriorityLevel); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null);
commitMutationEffects会遍历effectList,对每个Fiber节点执行如下三个操作:
Fiber节点
ContentReset effectTag
ref
effectTag
Placement
Update
Deletion
Hydrating[服务端渲染相关]
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) { // 遍历effectList while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // 根据 ContentReset effectTag重置文字节点 if (effectTag & ContentReset) { commitResetTextContent(nextEffect); } // 更新ref if (effectTag & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } } // 根据 effectTag 分别处理 const primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating); switch (primaryEffectTag) { // 插入DOM case Placement: { commitPlacement(nextEffect); nextEffect.effectTag &= ~Placement; break; } // 插入DOM 并 更新DOM case PlacementAndUpdate: { // 插入 commitPlacement(nextEffect); nextEffect.effectTag &= ~Placement; // 更新 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // SSR case Hydrating: { nextEffect.effectTag &= ~Hydrating; break; } // SSR case HydratingAndUpdate: { nextEffect.effectTag &= ~Hydrating; const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // 更新DOM case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // 删除DOM case Deletion: { commitDeletion(root, nextEffect, renderPriorityLevel); break; } } nextEffect = nextEffect.nextEffect; } }
Placement effect & commitPlacement
Placement effectTag: 插入标记,意味着该Fiber节点对应的DOM节点需要插入到页面中。
Placement effectTag
调用commitPlacement方法执行Placement effectTag操作。
commitPlacement
// 1. 获取父级DOM节点。其中finishedWork为传入的Fiber节点 const parentFiber = getHostParentFiber(finishedWork); // 父级DOM节点 const parentStateNode = parentFiber.stateNode; // 2. 获取Fiber节点的DOM兄弟节点 const before = getHostSibling(finishedWork); // 3. 根据DOM兄弟节点是否存在决定调用parentNode.insertBefore或parentNode.appendChild执行DOM插入操作 // parentStateNode是否是rootFiber if (isContainer) { insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { insertOrAppendPlacementNode(finishedWork, before, parent); }
Update effect & commitWork
Update effectTag: 更新标记,意味着该Fiber节点需要更新并将结果映射为DOM节点插入到页面中。
Update effectTag
调用commitWork方法执行Update effectTag操作。commitWork()会根据Fiber.tag分别处理不同的Fiber类型。
commitWork
commitWork()
Fiber.tag
Fiber类型
主要关注FunctionComponent和HostComponent
FunctionComponent
HostComponent
✅ FunctionComponent mutation
当fiber.tag为FunctionComponent,会调用commitHookEffectListUnmount。该方法会遍历effectList,执行所有useLayoutEffect hook的销毁函数。
fiber.tag
commitHookEffectListUnmount
useLayoutEffect hook
✅ HostComponent mutation
当fiber.tag为HostComponent,会调用commitUpdate。最终会在updateDOMProperties (opens new window)中将render阶段 completeWork (opens new window)中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。
commitUpdate
updateDOMProperties
render阶段 completeWork
updateQueue
Deletion effect & commitDeletion
Deletion effectTag: 删除标记,意味着该Fiber节点对应的DOM节点需要从页面中删除。
Deletion effectTag
调用commitDeletion方法执行Deletion effectTag操作。
commitDeletion
commitDeletion 方法会执行如下操作:
ClassComponent
componentWillUnmount
layout
遍历effectList,执行commitLayoutEffects()函数。
commitLayoutEffects()
该阶段的代码都是在DOM渲染完成(mutation阶段完成)后执行的。该阶段触发的生命周期钩子和hook可以直接访问到已经改变后的DOM。
DOM
mutation阶段
hook
root.current = finishedWork; // current Fiber树切换 nextEffect = firstEffect; // 遍历effectList do { try { // 执行 commitLayoutEffects() commitLayoutEffects(root, lanes); } catch (error) { invariant(nextEffect !== null, "Should be working on an effect."); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null); nextEffect = null;
root.current = finishedWork
在双缓存机制一节介绍过,workInProgress Fiber树在commit阶段完成渲染后会变为current Fiber树。
workInProgress Fiber树
current Fiber树
root.current = finishedWork的作用就是切换fiberRootNode指向的current Fiber树。
fiberRootNode
也就是说,commit 阶段渲染完成后切换树的操作发生在mutation阶段结束后,layout阶段开始前,即 root.current = finishedWork这行代码
以该行代码为界,分隔成mutation阶段和layout阶段,分割出了两大类生命周期函数:
componentWillUnmount会在mutation阶段执行。此时current Fiber树还指向前一次更新的Fiber树,在生命周期钩子内获取的DOM还是更新前的。
Fiber树
componentDidMount和componentDidUpdate会在layout阶段执行。此时current Fiber树已经指向更新后的Fiber树,在生命周期钩子内获取的DOM就是更新后的。
componentDidMount
componentDidUpdate
commitLayoutEffects
commitLayoutEffects一共做了两件事:
生命周期钩子
状态更新
this.setState
回调函数
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) { while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // 调用生命周期钩子和hook if (effectTag & (Update | Callback)) { const current = nextEffect.alternate; commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes); } // 赋值ref if (effectTag & Ref) { commitAttachRef(nextEffect); } nextEffect = nextEffect.nextEffect; } }
commitLayoutEffectOnFiber
commitLayoutEffectOnFiber方法会根据fiber.tag对不同类型的节点分别处理。
current === null?
mount
update
销毁
回调
HostRoot
rootFiber
// FunctionComponent 相关操作 switch (finishedWork.tag) { // 以下都是FunctionComponent及相关类型 case FunctionComponent: case ForwardRef: case SimpleMemoComponent: case Block: { // 执行useLayoutEffect的回调函数 commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); // 调度useEffect的销毁函数与回调函数 schedulePassiveEffects(finishedWork); return; }
useLayoutEffect与useEffect的区别: useLayoutEffect hook从mutation阶段的 销毁函数调用到本次更新layout阶段的回调函数调用是同步执行的。 而useEffect则需要先调度,在Layout阶段完成后再异步执行。
useLayoutEffect与useEffect的区别:
useLayoutEffect
useLayoutEffect hook从mutation阶段的 销毁函数调用到本次更新layout阶段的回调函数调用是同步执行的。
销毁函数
而useEffect则需要先调度,在Layout阶段完成后再异步执行。
Layout阶段
// HostRoot(即rootFiber) 相关操作 ReactDOM.render(<App />, document.querySelector("#root"), function() { console.log("i am mount~"); });
React 技术揭秘学习笔记
参考文章:React技术揭秘
导航
render阶段
performSyncWorkOnRoot() || performConcurrentWorkOnRoot()
performUnitOfWork()
beginWork()
reconcileChildren()
mountChildFibers()
reconcileChildFibers()
completeWork()
commit阶段
commitRoot(fiberRootNode)
内容
before mutation
遍历
effectList
并调用commitBeforeMutationEffects
函数处理。commitBeforeMutationEffects()
整体可以分为三部分:
DOM节点
渲染/删除后的autoFocus
、blur
逻辑。getSnapshotBeforeUpdate
生命周期钩子。useEffect
。❗️注意:是调度useEffect
而不是调用。mutation
遍历
effectList
并调用commitMutationEffects
函数处理。commitMutationEffects
commitMutationEffects
会遍历effectList
,对每个Fiber节点
执行如下三个操作:ContentReset effectTag
重置文字节点ref
effectTag
分别处理,其中effectTag
包括(Placement
|Update
|Deletion
|Hydrating[服务端渲染相关]
)Placement effect & commitPlacement
Placement effectTag
: 插入标记,意味着该Fiber节点
对应的DOM节点
需要插入到页面中。调用
commitPlacement
方法执行Placement effectTag
操作。Update effect & commitWork
Update effectTag
: 更新标记,意味着该Fiber节点
需要更新并将结果映射为DOM节点
插入到页面中。调用
commitWork
方法执行Update effectTag
操作。commitWork()
会根据Fiber.tag
分别处理不同的Fiber类型
。✅ FunctionComponent mutation
当
fiber.tag
为FunctionComponent
,会调用commitHookEffectListUnmount
。该方法会遍历effectList
,执行所有useLayoutEffect hook
的销毁函数。✅ HostComponent mutation
当
fiber.tag
为HostComponent
,会调用commitUpdate
。最终会在updateDOMProperties
(opens new window)中将render阶段 completeWork
(opens new window)中为Fiber节点
赋值的updateQueue
对应的内容渲染在页面上。Deletion effect & commitDeletion
Deletion effectTag
: 删除标记,意味着该Fiber节点
对应的DOM节点
需要从页面中删除。调用
commitDeletion
方法执行Deletion effectTag
操作。commitDeletion 方法会执行如下操作:
Fiber节点
及其子孙Fiber节点
中fiber.tag
为ClassComponent
的componentWillUnmount
(opens new window)生命周期钩子,从页面移除Fiber节点
对应DOM节点
ref
useEffect
的销毁函数layout
遍历
effectList
,执行commitLayoutEffects()
函数。该阶段的代码都是在
DOM
渲染完成(mutation阶段
完成)后执行的。该阶段触发的生命周期钩子和hook
可以直接访问到已经改变后的DOM
。root.current = finishedWork
在双缓存机制一节介绍过,
workInProgress Fiber树
在commit阶段
完成渲染后会变为current Fiber树
。root.current = finishedWork
的作用就是切换fiberRootNode
指向的current Fiber树
。以该行代码为界,分隔成
mutation阶段
和layout阶段
,分割出了两大类生命周期函数:componentWillUnmount
会在mutation阶段
执行。此时current Fiber树
还指向前一次更新的Fiber树
,在生命周期钩子内获取的DOM
还是更新前的。componentDidMount
和componentDidUpdate
会在layout阶段
执行。此时current Fiber树
已经指向更新后的Fiber树
,在生命周期钩子内获取的DOM
就是更新后的。commitLayoutEffects
commitLayoutEffects
一共做了两件事:生命周期钩子
和hook
相关操作, 触发状态更新
的this.setState
如果赋值了第二个参数回调函数
,也会在此时调用。)commitLayoutEffectOnFiber
生命周期钩子
hook
相关操作状态更新
的this.setState
如果赋值了第二个参数回调函数
,也会在此时调用。commitLayoutEffectOnFiber
方法会根据fiber.tag
对不同类型的节点分别处理。ClassComponent
,他会通过current === null?
区分是mount
还是update
,调用componentDidMount
或componentDidUpdate
FunctionComponent
及相关类型,他会调用useLayoutEffect hook
的回调函数
,调度useEffect
的销毁
与回调
函数HostRoot
,即rootFiber
,如果赋值了第三个参数回调函数
,也会在此时调用。