作者并不是前端技术专家,也只是一名喜欢学习新东西的前端技术小白,想要学习源码只是为了应付急转直下的前端行情和找工作的需要,这篇专栏是作者学习的过程中自己的思考和体会,也有很多参考其他教程的部分,如果存在错误或者问题,欢迎向作者指出,作者保证内容 100% 正确,请不要将本专栏作为参考答案。
本专栏的阅读需要你具有一定的 React 基础、 JavaScript 基础和前端工程化的基础,作者并不会讲解很多基础的知识点,例如:babel 是什么,jsx 的语法是什么,需要时请自行查阅相关资料。
本专栏很多部分参考了大量其他教程,若有雷同,那是作者抄袭他们的,所以本教程完全开源,你可以当成作者对各类教程进行了整合、总结并加入了自己的理解。
本节的我们将谈谈 commit 中 React 的操作,因为在之前的教程中,我们已经讲到了 React 怎么样把 element 节点变成 Fiber 树,怎么样通过 DIFF 算法更新我们的 Fiber 树,那么现在我们需要将我们的 Fiber 树的更新同步到我们的真实 DOM 上展示给用户,这个阶段就是 Commit 阶段
现在我们把我们的视线拉回到我们的第四篇教程 —— updateContainer
相关的部分,我们可以看到我们之前一笔带过的两个函数:performConcurrentWorkOnRoot
、 performSyncWorkOnRoot
,我们现在来看看他们的结构:
ConcurrentWork
需要额外处理一下并发任务相关的进程调度和优先级的问题renderRootSync
和 renderRootConcurrent
中,我们生成了我们的 Fiber 树,所以在这两个函数之后,我们可以拿到我们的 Fiber 树了commitRoot
这个函数,这个函数传入我们刚刚生成的 Fiber 树的架构 。其中 sync 的任务直接调用,而 Concurrent 的任务因为需要通过 finishConcurrentRender
这个函数,根据状态判定是不是完成了我们的渲染,然后调用 commitRootWhenReady
(其中调用了 commitRoot)进入我们的 commit
阶段(关于这两种任务的区别我们会后续单独来讲)export function performSyncWorkOnRoot(root: FiberRoot): null {
// 省略....
flushPassiveEffects();
// 优先级相关
let lanes = getNextLanes(root, NoLanes);
if (!includesSyncLane(lanes)) {
ensureRootIsScheduled(root);
return null;
}
// 生成 Fiber 的函数
let exitStatus = renderRootSync(root, lanes);、
//下面都是处理生成失败的处理
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
//...
}
if (exitStatus === RootFatalErrored) {
//...
}
if (exitStatus === RootDidNotComplete) {
//...
}
// 生成完毕,返回我们的 WorkInProgress 树的根节点
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 进入 commit 阶段
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
ensureRootIsScheduled(root);
return null;
}
export function performConcurrentWorkOnRoot(
root: FiberRoot,
didTimeout: boolean,
): RenderTaskFn | null {
// 省略处理优先级相关、调度相关的 ....
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout);
// 生成 Fiber 的函数
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
//下面都是处理生成失败的处理....省略
// 生成完毕,返回我们的 WorkInProgress 树的根节点
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 进入 commit 阶段
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
}
ensureRootIsScheduled(root);
return getContinuationForRoot(root, originalCallbackNode);
}
// finishConcurrentRender 判定是不是可以进入 commit 阶段,具体的分析之后我们会单独开一篇
function finishConcurrentRender(
root: FiberRoot,
exitStatus: RootExitStatus,
finishedWork: Fiber,
lanes: Lanes,
) {
// 根据状态判定,
switch (exitStatus) {
case RootInProgress:
case RootFatalErrored: {
throw new Error('Root did not complete. This is a bug in React.');
}
case RootErrored: {
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes,
);
break;
}
case RootSuspended: {
// 省略....
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes,
);
break;
}
case RootSuspendedWithDelay: {
// 省略....
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes,
);
break;
}
case RootCompleted: {
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes,
);
break;
}
default: {
throw new Error('Unknown root exit status.');
}
}
}
那么可见 commitRoot
这个函数就是我们在生成了我们的 Fiber 之后函数的统一入口,它又调用了 commitRootImpl
这个函数,我们现在来看看这个函数做了什么。首先需要明确,在 rootFiber.firstEffect 上保存了一条需要执行副作用的Fiber节点的单向链表 effectList ,这些 Fiber 节点的updateQueue 中保存了变化的props:
BeforeMutationEffects
主要是更新class组件实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数MutationEffects
主要是完成副作用的执行,主要包括重置文本节点以及真实 dom 节点的插入、删除和更新等操作。LayoutEffects
主要是去触发 componentDidMount、componentDidUpdate 以及各种回调函数等function commitRoot(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
) {
const previousUpdateLanePriority = getCurrentUpdatePriority();
const prevTransition = ReactCurrentBatchConfig.transition;
try {
ReactCurrentBatchConfig.transition = null;
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(
root,
recoverableErrors,
transitions,
previousUpdateLanePriority,
);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<mixed>,
transitions: Array<Transition> | null,
renderPriorityLevel: EventPriority,
) {
// 调用flushPassiveEffects执行完所有effect的任务
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
// 优先级调度
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
if (enableSchedulingProfiler) {
markCommitStarted(lanes);
}
if (finishedWork === null) {
// 异常处理
}
//重置 FiberRoot
root.finishedWork = null;
root.finishedLanes = NoLanes;
root.callbackNode = null;
root.callbackPriority = NoLane;
//省略优先级相关的....
// 重置全局变量
if (root === workInProgressRoot) {
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
} else {
}
// 处理 useEffect相关内容
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
pendingPassiveTransitions = transitions;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
// 第一次遍历,执行 commitBeforeMutationEffects
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;
}
// 第二次遍历,执行 commitMutationEffects
commitMutationEffects(root, finishedWork, lanes);
if (enableCreateEventHandleAPI) {
if (shouldFireAfterActiveInstanceBlur) {
afterActiveInstanceBlur();
}
}
resetAfterCommit(root.containerInfo);
root.current = finishedWork;
if (enableSchedulingProfiler) {
markLayoutEffectsStarted(lanes);
}
// 第三次遍历,执行 commitLayoutEffects
commitLayoutEffects(finishedWork, root, lanes);
if (enableSchedulingProfiler) {
markLayoutEffectsStopped();
}
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
rootCommittingMutationOrLayoutEffects = null;
}
requestPaint();
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
} else {
// 没有任何副作用
root.current = finishedWork;
if (enableProfilerTimer) {
recordCommitTime();
}
}
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
// 处理 useEffect相关内容
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
} else {
releaseRootPooledCache(root, remainingLanes);
}
// 省略优先级相关和DEV模式相关....
// 触发一次新的调度,确保任何附加的任务被调度
ensureRootIsScheduled(root, now());
// 处理componentDidMount等生命周期或者useLayoutEffect等同步任务
flushSyncCallbacks();
return null;
}
我们依次来看每个阶段,在BeforeMutationEffects
这个阶段,我们的来看看它做了什么:
这阶段会调用 commitBeforeMutationEffects
函数处理
commitBeforeMutationEffects
函数会依次调用 commitBeforeMutationEffects_begin
,commitBeforeMutationEffects_complete
,commitBeforeMutationEffectsOnFiber
,getSnapShotBeforeUpdate
函数,也就是触发了 getSnapShotBeforeUpdate
这个生命周期
commitBeforeMutationEffects_begin
函数里,获取了当前阶段阶段需要删除的孩子(在上一个阶段的 diff 中生成),然后对他们执行相关的操作,这里主要是处理DOM节点渲染/删除后的 focus
和blur
逻辑。之后深度遍历直到没有孩子为止
commitBeforeMutationEffects_complete
中当我们遍历到的节点没有孩子,我们就遍历它的兄弟,也就是说 commitBeforeMutationEffects_begin
函数和 commitBeforeMutationEffects_complete
函数一起对我们的Fiber 进行了遍历(遍历的方式和之前的遍历 element 的时候大体一致)
commitBeforeMutationEffectsOnFiber
则是用于处理每一个 Fiber,主要是对 ClassComponent 组件进行处理,更新实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数:
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
) {
// 准备提交
focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
// 开始提交
commitBeforeMutationEffects_begin();
const shouldFire = shouldFireAfterActiveInstanceBlur;
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
if (enableCreateEventHandleAPI) {
// 获取需要删除的内容
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
commitBeforeMutationEffectsDeletion(deletion);
}
}
}
const child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
child.return = fiber;
nextEffect = child;
} else {
// 提交结束
commitBeforeMutationEffects_complete();
}
}
}
// 提交完毕
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
setCurrentDebugFiberInDEV(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentDebugFiberInDEV();
const sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
//...省略
if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(finishedWork);
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
break;
}
case ClassComponent: {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
//触发 getSnapshotBeforeUpdate
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
break;
default: {
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
resetCurrentDebugFiberInDEV();
}
}
前一个阶段只是做了一些准备工作,而在MutationEffects
这个阶段,我们就需要将我们的修改同步到我们的 DOM 上了,我们来看看它做了什么:
MutationEffects
这个阶段会进行不同的处理,但有一些公共逻辑会执行的,那就是删除操作和插入操作recursivelyTraverseMutationEffects
方法,这个方法会执行删除逻辑,完成删除逻辑后,commitReconciliationEffects
,这个方法负责往真实 DOM 树中插入 DOM 节点export function commitMutationEffects(
root: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
setCurrentDebugFiberInDEV(finishedWork);、
//直接进入 commitMutationEffectsOnFiber
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
setCurrentDebugFiberInDEV(finishedWork);
inProgressLanes = null;
inProgressRoot = null;
}
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
// 获取 current 树的内容
const current = finishedWork.alternate;
// 获取元素被打上的标记
const flags = finishedWork.flags;
// 根据不同类别的元素做不同的擦操作
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// 删除操作
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
// 插入操作
commitReconciliationEffects(finishedWork);
// ....
}
case ClassComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
// ....
return;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
// ....
return;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
// ....
return;
}
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
// ....
return;
}
// .... 省略
default: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
return;
}
}
}
我们先来看 recursivelyTraverseMutationEffects
的操作,它获取了之前我们上一个阶段存放在 deletions
中的孩子,然后依次调用了 commitDeletionEffects
方法,这个方法又会调用 commitDeletionEffectsOnFiber
这个函数,这个函数需要分类讨论:
componentWillUnmount
生命周期函数function recursivelyTraverseMutationEffects(
root: FiberRoot,
parentFiber: Fiber,
lanes: Lanes,
) {
// 取出要删除的孩子
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
try {
// 删除孩子
commitDeletionEffects(root, parentFiber, childToDelete);
} catch (error) {
captureCommitPhaseError(childToDelete, parentFiber, error);
}
}
}
}
function commitDeletionEffectsOnFiber(
finishedRoot: FiberRoot,
nearestMountedAncestor: Fiber,
deletedFiber: Fiber,
) {
onCommitUnmount(deletedFiber);
switch (deletedFiber.tag) {
case HostComponent: {
if (!offscreenSubtreeWasHidden) {
// ref 设置回 null
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}
}
case HostText: {
if (supportsMutation) {
const prevHostParent = hostParent;
const prevHostParentIsContainer = hostParentIsContainer;
hostParent = null;
// 往下遍历子节点,执行删除
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
hostParent = prevHostParent;
hostParentIsContainer = prevHostParentIsContainer;
// 删除真正的 DOM,调用了原生的 removeChild 方法
if (hostParent !== null) {
if (hostParentIsContainer) {
removeChildFromContainer(
((hostParent: any): Container),
(deletedFiber.stateNode: Instance | TextInstance),
);
} else {
removeChild(
((hostParent: any): Instance),
(deletedFiber.stateNode: Instance | TextInstance),
);
}
}
} else {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
}
return;
}
// ....
// 函数组件
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
if (!offscreenSubtreeWasHidden) {
// 读取 updateQueue 队列,队列用链表的方式保存
const updateQueue: FunctionComponentUpdateQueue | null = (deletedFiber.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const {destroy, tag} = effect;
if (destroy !== undefined) {
if ((tag & HookInsertion) !== NoHookEffect) {
// 处理 useInsertionEffect 副作用
safelyCallDestroy(
deletedFiber,
nearestMountedAncestor,
destroy,
);
} else if ((tag & HookLayout) !== NoHookEffect) {
// 处理 useLayoutEffect 副作用
if (enableSchedulingProfiler) {
markComponentLayoutEffectUnmountStarted(deletedFiber);
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
deletedFiber.mode & ProfileMode
) {
startLayoutEffectTimer();
safelyCallDestroy(
deletedFiber,
nearestMountedAncestor,
destroy,
);
recordLayoutEffectDuration(deletedFiber);
} else {
safelyCallDestroy(
deletedFiber,
nearestMountedAncestor,
destroy,
);
}
if (enableSchedulingProfiler) {
markComponentLayoutEffectUnmountStopped();
}
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
}
// 遍历子节点执行删除逻辑
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
return;
}
// 类组件
case ClassComponent: {
if (!offscreenSubtreeWasHidden) {
// 移除 ref
safelyDetachRef(deletedFiber, nearestMountedAncestor);
const instance = deletedFiber.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
// 调用类组件实例的 componentWillUnmount 方法
safelyCallComponentWillUnmount(
deletedFiber,
nearestMountedAncestor,
instance,
);
}
}
//遍历子节点执行删除逻辑
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
return;
}
// ....省略
default: {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
return;
}
}
}
之后我们来看看另一个插入的逻辑 commitReconciliationEffects
,它只会在原生组件和 fiber 根节点上操作,并不操作函数组件和类组件,它的路逻辑是:
parent.textContent = ''
来重置它的内容insertBefore
方法将内容插入到兄弟节点之前,如果没有,则调用父节点的 appendChild
进行插入function commitReconciliationEffects(finishedWork: Fiber) {
const flags = finishedWork.flags;
if (flags & Placement) {
try {
//执行操作
commitPlacement(finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
// 移除 Placement 标志
finishedWork.flags &= ~Placement;
}
if (flags & Hydrating) {
finishedWork.flags &= ~Hydrating;
}
}
function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}
//获取父 fiber
const parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostComponent: {
const parent: Instance = parentFiber.stateNode;
// 判断父 fiber 是否有 ContentReset(内容重置)标记
if (parentFiber.flags & ContentReset) {
// 通过 parent.textContent = '' 的方式重置
resetTextContent(parent);
parentFiber.flags &= ~ContentReset;
}
// 找它的下一个兄弟 DOM 节点,
const before = getHostSibling(finishedWork);
// 如果存在,用 insertBefore 方法;如果没有,就调用原生的 appendChild 方法
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot:
case HostPortal: {
const parent: Container = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
break;
}
default:
throw new Error(
'Invalid host parent fiber. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
}
现在我们回到我们的 commitMutationEffectsOnFiber
,我们来看看在完成了删除和 插入操作后三中不同的节点又做了什么操作:
commitUpdate
执行更新逻辑,否则直接重置这个原生节点commitTextUpdate
函数,更新其文本内容 switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// ....
if (flags & Update) {
try {
// 找出 useInsertionEffect 的 destroy 方法去调用
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
finishedWork.return,
);
// 执行 useInsertionEffect 的回调函数,并将返回值保存到 effect.destory 里。
commitHookEffectListMount(
HookInsertion | HookHasEffect,
finishedWork,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
// 执行 useLayoutEffect 对应的 destroy 方法
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
recordLayoutEffectDuration(finishedWork);
} else {
try {
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
// 类组件不会进行操作
case ClassComponent: {
// ...
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}
}
return;
}
//原生组件
case HostComponent: {
//...
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}
}
if (supportsMutation) {
// 判断是不是需要重置
if (finishedWork.flags & ContentReset) {
const instance: Instance = finishedWork.stateNode;
try {
resetTextContent(instance);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
// 判断是不是需要更新
if (flags & Update) {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps =
current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
try {
// 更新操作
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
} catch (error) {
captureCommitPhaseError(
finishedWork,
finishedWork.return,
error,
);
}
}
}
}
}
return;
}
//原生文本
case HostText: {
//....
if (flags & Update) {
if (supportsMutation) {
if (finishedWork.stateNode === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
const oldText: string =
current !== null ? current.memoizedProps : newText;
try {
// 更新操作
commitTextUpdate(textInstance, oldText, newText);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
//...
}
最后我们来看看这个更新函数,它的逻辑很简单:它获取了真实 dom 节点实例、props 以及 updateQueue ,将 props 的属性一一对应应用到真实 DOM 上
export function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// 对 props 的进行对比更新
updateProperties(domElement, updatePayload, type, oldProps, newProps);
// 应用更新
updateFiberProps(domElement, newProps);
}
// 对比更新
export function updateProperties(
domElement: Element,
updatePayload: Array<any>,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
): void {
// 针对表单组件进行特殊处理
if (
tag === 'input' &&
nextRawProps.type === 'radio' &&
nextRawProps.name != null
) {
ReactDOMInputUpdateChecked(domElement, nextRawProps);
}
// 判断是否为用户自定义的组件,即是否包含 "-"
const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
// 将 diff 结果应用于真实 dom
updateDOMProperties(
domElement,
updatePayload,
wasCustomComponentTag,
isCustomComponentTag,
);
// 针对表单的特殊处理
switch (tag) {
case 'input':
ReactDOMInputUpdateWrapper(domElement, nextRawProps);
break;
case 'textarea':
ReactDOMTextareaUpdateWrapper(domElement, nextRawProps);
break;
case 'select':
ReactDOMSelectPostUpdateWrapper(domElement, nextRawProps);
break;
}
}
function updateDOMProperties(
domElement: Element,
updatePayload: Array<any>,
wasCustomComponentTag: boolean,
isCustomComponentTag: boolean,
): void {
// 对 updatePayload 遍历
for (let i = 0; i < updatePayload.length; i += 2) {
const propKey = updatePayload[i];
const propValue = updatePayload[i + 1];
if (propKey === STYLE) {
// 处理 style 样式更新
setValueForStyles(domElement, propValue);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// 处理 innerHTML 改变
setInnerHTML(domElement, propValue);
} else if (propKey === CHILDREN) {
// 处理 textContent
setTextContent(domElement, propValue);
} else {
// 处理其他节点属性
setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
}
}
}
最后我们来看看 LayoutEffects
这个阶段,这个阶段我们已经可以调用到我们的真实 DOM 了,它主要是执行 componentDidMount
和 componentDidUpdate
生命周期,我们来看看逻辑:
BeforeMutationEffects
基本一致。都是使用了深度优先遍历的方式,这里我们不再具体阐述了,我们直接来看commitLayoutEffectOnFiber
这个函数useLayoutEffect
hooks 即可componentDidMount
或者 componentDidUpdate
生命周期,之后还要处理 commitUpdateQueue
中的回调函数ReactDOM.render
的调回函数commitUpdateQueue
函数会对 finishedQueue 上面的 effects 进行遍历,若有 callback,则执行 callback。同时会重置 finishedQueue 上面的 effects 为 nullfunction commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
if (
!enableSuspenseLayoutEffectSemantics ||
!offscreenSubtreeWasHidden
) {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
// 提交 useLayoutEffect
commitHookEffectListMount(
HookLayout | HookHasEffect,
finishedWork,
);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
}
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (!offscreenSubtreeWasHidden) {
if (current === null) {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
// 首次渲染,触发 componentDidMount 生命周期
instance.componentDidMount();
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidMount();
}
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(
finishedWork.type,
current.memoizedProps,
);
const prevState = current.memoizedState;
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
// 非首次渲染,触发 componentDidUpdate 生命周期
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
}
}
}
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
// 执行 commitUpdateQueue 处理回调
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostRoot: {
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
// 调用 commitUpdateQueue 处理 ReactDOM.render 的回调
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// commitMount 处理 input 标签有 auto-focus 的情况
if (current === null && finishedWork.flags & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
break;
}
case HostText:
break;
}
case HostPortal: {
break;
}
default:
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
上述就是我们的 commit 阶段的内容,我们来总结一下它运行流程,大致可以分为五个阶段来说
BeforeMutationEffects
函数之前,我们先处理 useEffect
相关的任务并且进行一些初始化的工作BeforeMutationEffects
阶段,我们深度优先遍历我们的 Fiber 树,处理DOM节点渲染/删除后的 focus
和blur
逻辑,然后处理我们 clas 组件的 getSnapshotBeforeUpdate
生命周期MutationEffects
阶段,这个阶段里,我们执行 recursivelyTraverseMutationEffects
方法对需要删除的节点进行处理,再执行 commitReconciliationEffects
逻辑插入DOM,之后分别进行处理
commitUpdate
执行更新逻辑(复用)LayoutEffects
阶段,主要是执行类组件的 componentDidMount
和 componentDidUpdate
生命周期或者函数组件的 useLayoutEffect
hooks ,然后对 finishedQueue 上面的 effects 的回调进行处理useEffect
相关的内容,再执行 commit 阶段相关的同步任务即可经过上述的操作,React 的节点从我们构建的 Fiber 同步到了真实的 DOM 上展示给了用户,相关的生命周期和 Hooks 也得到了触发,至此,一个从 jsx 开始的 React 的渲染过程已经讲完了,我们之后会在讲完调度后再完整的来梳理一遍。
现在我们来看看还有什么问题没有解决:
scheduler
阶段,之后我们会结合我们之前讲解渲染的过程来讲解我们的 scheduler
是怎么安排任务和调度进程的那么这两个问题我们会在后续的教程里给出解答