commitWork 阶段
beginWork
和completeWork
阶段都正常结束后,此时所有的fiber
和真实节点创建完成,进入到commit
阶段:
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
当结果为RootCompleted
时,finishConcurrentRender
函数会调用commitRoot(root)
方法:
function commitRoot(root) {
const previousUpdateLanePriority = getCurrentUpdatePriority();
const prevTransition = ReactCurrentBatchConfig.transition;
try {
ReactCurrentBatchConfig.transition = 0;
// 优先级为同步,不可被打断
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root, previousUpdateLanePriority);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}
commit
执行时,会将当前优先级置为DiscreteEventPriority
,也就是同步优先级,为同步执行,不可被打断。
flushPassiveEffects
commit
第一步会执行flushPassiveEffects
方法:
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
那么什么时候rootWithPendingPassiveEffects !== null
成立呢?在commitRootImpl
方法后半段会进行赋值:
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
}
如果rootDoesHavePassiveEffects
成立,那么会将root
赋值给rootWithPendingPassiveEffects
。那么下一轮commit
时,rootWithPendingPassiveEffects !== null
就会成立了。还是在这个函数中,再看看rootDoesHavePassiveEffects
变量:
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
如果flags/subtreeFlags
中存在PassiveMask
,即Passive|ChildDeletion
,那么rootDoesHavePassiveEffects
为true
。也就是说如果使用了useEffect
或者是节点有删除的情况,那么就会执行flushPassiveEffects
方法:
export function flushPassiveEffects(): boolean {
// 如果 rootWithPendingPassiveEffects 存在,说明使用了 useEffect 或者有子节点被删除
if (rootWithPendingPassiveEffects !== null) {
const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
const prevTransition = ReactCurrentBatchConfig.transition;
const previousPriority = getCurrentUpdatePriority();
try {
// transition 置为 0
ReactCurrentBatchConfig.transition = 0;
// 设置 update 优先级,获取 lane 的时候会用得到
setCurrentUpdatePriority(priority);
return flushPassiveEffectsImpl();
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
return false;
}
设置更新的优先级后,会执行两个方法:一个是卸载方法,一个是挂载方法。
// 从下到 sibling 到 return 这种深度遍历的方式,一次执行 effect 的 destroy 方法
commitPassiveUnmountEffects(root.current);
// 遍历 root.current 的 updateQueue,执行上面的 effect 的 create 方法
commitPassiveMountEffects(root, root.current);
commitPassiveUnmountEffects
commitPassiveUnmountEffects_begin
commitPassiveUnmountEffects
实际调用的是commitPassiveUnmountEffects_begin
方法,该方法首先会向下遍历,对于有ChildDeletion
标记的都会遍历fiber.deletions
(记录了需要删除的老fiber
)执行真实节点的删除:
if ((nextEffect.flags & ChildDeletion) !== NoFlags) {
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0;i < deletions.length;i++) {
const fiberToDelete = deletions[i];
nextEffect = fiberToDelete;
// 进行具体 delete
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
fiberToDelete,
fiber,
);
}
}
}
commitPassiveUnmountEffectsInsideOfDeletedTree_begin
commitPassiveUnmountEffectsInsideOfDeletedTree_begin
方法同样会向下遍历,对于遍历到的function
组件,执行卸载方法:
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
function commitPassiveUnmountInsideDeletedTreeOnFiber(
current: Fiber,
nearestMountedAncestor: Fiber | null,
): void {
switch (current.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// 执行卸载方法
commitHookEffectListUnmount(
HookPassive,
current,
nearestMountedAncestor,
);
break;
}
}
}
commitHookEffectListUnmount
commitHookEffectListUnmount
方法会循环执行fiber
上的effects
(如果使用了useEffect/useLayoutEffect
等,会创建effect
,并形成链表挂载到fiber.updateQueue.effects.lastEffect
上):
const firstEffect = lastEffect.next;
// 找到第一个 effect
let effect = firstEffect;
do {
// 如果 tag 包含 flags
if ((effect.tag & flags) === flags) {
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
由于这里的tag
为HookPassive
,因此只有useEffect
里的destroy
方法才会被执行,而useLayoutEffect
里的destroy
不会被执行。
commitPassiveUnmountEffectsInsideOfDeletedTree_complete
在执行了destroy
方法后,会删除fiber
引用并且删除对应真实节点的引用:
function detachFiberAfterEffects(fiber: Fiber) {
fiber.child = null;
fiber.deletions = null;
fiber.sibling = null;
// ...
if (fiber.tag === HostComponent) {
const hostInstance: Instance = fiber.stateNode;
if (hostInstance !== null) {
detachDeletedInstance(hostInstance);
}
}
fiber.stateNode = null;
// ...
}
如果是普通便签,且fiber.stateNode
存在,那么需要调用detachDeletedInstance
方法删除node
节点上的一些引用:
export function detachDeletedInstance(node: Instance): void {
delete (node: any)[internalInstanceKey];
delete (node: any)[internalPropsKey];
delete (node: any)[internalEventHandlersKey];
delete (node: any)[internalEventHandlerListenersKey];
delete (node: any)[internalEventHandlesSetKey];
}
回到commitPassiveUnmountEffects_begin
方法,在执行了老fiber
子节点的删除后,还需要将老fiber
与子节点,未删除子节点与已删除子节点之间的引用删除:
const previousFiber = fiber.alternate;
if (previousFiber !== null) {
let detachedChild = previousFiber.child;
if (detachedChild !== null) {
previousFiber.child = null;
do {
const detachedSibling = detachedChild.sibling;
// 挨个将 sibling 指向置空
detachedChild.sibling = null;
detachedChild = detachedSibling;
} while (detachedChild !== null);
}
}
commitPassiveUnmountEffects_complete
向下查找直到找到不是PassiveMask
的节点,此时执行commitPassiveUnmountEffects_complete
方法:
if ((fiber.flags & Passive) !== NoFlags) {
// 循环执行 unmount 下的 destroy 方法
commitPassiveUnmountOnFiber(fiber);
}
该方法会向上查找,是否有flags
包含Passive
的fiber
。如果有,同样会调用commitPassiveUnmountOnFiber
执行effect
的destroy
方法。这样整棵树中有ChildDeletion
和Passive
标记的fiber
中的卸载方法就全都执行了。
commitPassiveMountEffects
commitPassiveMountEffects_begin
commitPassiveMountEffects_begin
方法作用是向下查找,找到一个非PassiveMask
标记的节点:
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
}
commitPassiveMountEffects_complete
commitPassiveMountEffects_complete
方法会从下往上查找有Passive
标记的节点,并执行其commitPassiveMountOnFiber
方法:
if ((fiber.flags & Passive) !== NoFlags) {
commitPassiveMountOnFiber(root, fiber);
}
commitPassiveMountOnFiber
对于函数式组件都会执行commitHookEffectListMount
方法,此时hook
的tag
为HookPassive | HookHasEffect
:
function commitPassiveMountOnFiber(
finishedRoot: FiberRoot,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
break;
}
}
}
commitHookEffectListMount
commitHookEffectListMount
则是循环调用effects
里的create
方法并生成destroy
:
const firstEffect = lastEffect.next;
// 找到第一个 effect
let effect = firstEffect;
do {
if ((effect.tag & tag) === tag) {
// 调用 create 方法
const create = effect.create;
effect.destroy = create();
effect = effect.next;
}
} while (effect !== firstEffect);
变量重置
一些列全局的变量重置:
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
root.finishedWork = null;
root.finishedLanes = NoLanes;
// 任务重置
root.callbackNode = null;
root.callbackPriority = NoLane;
// 移除 lane
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);
if (root === workInProgressRoot) {
// 重置
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
}
重置完成后判断是否具有PassiveMask
(最开始的flushPassiveEffects
与这里的判断有关)。如果之前rootDoesHavePassiveEffects
为false
,表示effect
还未被记录,此时需要调用一次flushPassiveEffects
方法。
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
执行flushPassiveEffects
时,会先遍历执行destroy
,由于第一次创建没有destroy
,所以不会执行。然后会遍历执行create
,并且生成destroy
。
beforeMutation 阶段
commitBeforeMutationEffects
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
如果subtreeHasEffects
或rootHasEffect
存在,说明有更新。首先会进入beforeMutation
阶段,调用commitBeforeMutationEffects
方法:
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
) {
// ...
commitBeforeMutationEffects_begin();
// ...
}
其核心是调用commitBeforeMutationEffects_begin
方法。
commitBeforeMutationEffects_begin
与flushPassiveEffects
中类似,这里以_begin
结尾的函数都是向下遍历,直到找到一个不符合BeforeMutationMask
的节点。
export const BeforeMutationMask = Update | Snapshot
一种是标记了Update
的节点,一种是标记SnapShot
的节点,如在class
组件中定义了getSnapshotBeforeUpdate
函数,那么在beginWork
阶段就会将fiber
标记为SnapShot
。
commitBeforeMutationEffects_complete
以_complete
结尾的函数都是从下往上遍历,然后执行某个函数。这里执行的函数为commitBeforeMutationEffectsOnFiber
。
commitBeforeMutationEffectsOnFiber
if ((flags & Snapshot) !== NoFlags) {
switch (finishedWork.tag) {
case ClassComponent: {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
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;
}
}
}
如果SnapShot
标记主要有两种情况:一种是HostRoot
,在completeWork
阶段会将其标记:
if (current === null || current.child === null) {
workInProgress.flags |= Snapshot;
}
如果current或current.child
不存在,在commit
阶段会将root.containerInfo
真实节点的内容置空。
另一种情况是class
组件定义了getSnapshotBeforeUpdate
,此时会直接调用该函数,传入的值为先前的props
和state
。
mutation 阶段
commitMutationEffects
commitMutationEffects
执行的是commitMutationEffects_begin
方法:
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0;i < deletions.length;i++) {
const childToDelete = deletions[i];
commitDeletion(root, childToDelete, fiber);
}
}
首先是循环遍历,向下执commitDeletion
方法,等同于执行了unmountHostComponents
方法。
unmountHostComponents
unmountHostComponents
方法作用是删除fiber
对应的真实节点。首先是找到这个fiber
的父真实节点:
if (!currentParentIsValid) {
let parent = node.return;
findParent: while (true) {
const parentStateNode = parent.stateNode;
switch (parent.tag) {
case HostComponent:
currentParent = parentStateNode;
currentParentIsContainer = false;
break findParent;
case HostRoot:
currentParent = parentStateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
case HostPortal:
currentParent = parentStateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
}
parent = parent.return;
}
currentParentIsValid = true;
}
只有HostComponent、HostRoot、HostPortal
才会渲染成真实节点,其他的标签都是React
内部定义,不会渲染出来。
然后判断当前节点的类型,如果是HostComponent
或HostText
类型:
if (node.tag === HostComponent || node.tag === HostText) {
// 1. 递归触发卸载事件。(useLayoutEffect/componentWillUnmount)
commitNestedUnmounts(finishedRoot, node, nearestMountedAncestor);
// 2. 触发完事件,就需要移除真实节点
if (currentParentIsContainer) {
removeChildFromContainer(
((currentParent: any): Container),
(node.stateNode: Instance | TextInstance),
);
} else {
// 移除真实节点
removeChild(
((currentParent: any): Instance),
(node.stateNode: Instance | TextInstance),
);
}
}
首先是调用commitNestedUnmounts
方法,这个方法会遍历该节点下的所有fiber
,然后调用commitUnmount
方法。(注意:向下遍历遇到HostPortal时不会调用commitUnmount)。
commitUnmount
对于函数式组件,会循环执行带有Layout
的tag
的effect
。
do {
const { destroy, tag } = effect;
if (destroy !== undefined) {
// 执行 layout 的 destroy,也就是 useLayoutEffect
if ((tag & HookLayout) !== NoHookEffect) {
safelyCallDestroy(current, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
对于class
组件,一是移除ref
,二是调用componentWillUnmount
方法:
safelyDetachRef(current, nearestMountedAncestor);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(
current,
nearestMountedAncestor,
instance,
);
}
对于HostPortal
,则是调用unmountHostComponents
处理它的子节点。
commitMutationEffects_complete
等删除节点操作完成后,回到commitMutationEffects_begin
方法,会开始执行commitMutationEffects_complete
方法。该方法会向上查找,并且调用commitMutationEffectsOnFiber
方法。
commitMutationEffectsOnFiber
对于不同的flags
,mutation
阶段会做不同的处理:
ContentReset
表示需要按文本内容处理,并将内容置空:
commitResetTextContent(finishedWork);
Visibility
suspense
相关处理,suspense
章节再做讲解。
Placement
表示插入节点。第一步需要找到该节点的父fiber
节,注意,这里的父fiber
必须是能够渲染成真实DOM
的fiber
。
const parentFiber = getHostParentFiber(finishedWork);
function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
}
function isHostParent(fiber: Fiber): boolean {
return (
fiber.tag === HostComponent ||
fiber.tag === HostRoot ||
fiber.tag === HostPortal
);
}
第二步通过父fiber
获取真实节点:
const parentStateNode = parentFiber.stateNode;
// 找到真实父节点
switch (parentFiber.tag) {
case HostComponent:
parent = parentStateNode;
isContainer = false;
break;
case HostRoot:
// 注意这里是添加到根节点上去了。
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
// 注意这里是添加到根节点上去了。
parent = parentStateNode.containerInfo;
isContainer = true;
break;
}
第三步获取兄弟结点:
const before = getHostSibling(finishedWork);
function getHostSibling(fiber: Fiber): ?Instance {
siblings: while (true) {
// 如果 sibling 不存在
while (node.sibling === null) {
// 如果 return 不存在 获取 父亲是可以渲染成真实节点的 fiber
// 那么说明 sibling 确实是不存在的
if (node.return === null || isHostParent(node.return)) {
return null;
}
node = node.return;
}
// sibling 存在时
node.sibling.return = node.return;
node = node.sibling;
// 向下查找,查找合适的主节点
while (
node.tag !== HostComponent &&
node.tag !== HostText &&
node.tag !== DehydratedFragment
) {
// 如果是标记为Placement的,那么是不可用的。
if (node.flags & Placement) {
continue siblings;
}
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
// 查找孩子节点(因为react中fiber与实际的真实并不是完全对应的,如有的会形成一层包装)
node.child.return = node;
node = node.child;
}
}
// 如果找到的节点是不需要插入的节点,则返回该节点。
if (!(node.flags & Placement)) {
// Found it!
return node.stateNode;
}
}
}
需要注意的是,能渲染的真实节点的结构跟fiber结构并不会完全对应,可能会存在一些层次的包装,所以需要进行比较复杂的查找。
第四步则是正式的插入节点了:
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}
Update
当标记为Update
时,执行commitWork
。
当为函数组件时
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
触发useLayoutEffect
的destroy
方法。
当为普通真实节点时
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
// 如果有属性被更新了
finishedWork.updateQueue = null;
// 更新真实节点的属性
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
如果属性有更新,那么会调用commitUpdate
(DOM
属性处理的方法)更新真实DOM
上的属性,如style,href
等等。
layout 阶段
commitLayoutEffects
commitLayoutEffects
调用commitLayoutEffects_begin
方法,还是一样,找到第一个不是LayoutMask
的子节点,然后向上执行commitLayoutEffectOnFiber
方法。
commitLayoutEffectOnFiber
函数组件
为函数组件时,执行commitHookEffectListMount
方法,也就是useLayoutEffect
的create
方法。
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
class 组件
如果flags
存在Update
,说明定义了生命周期函数。当current
为null
的时候,说明是挂载阶段,调用componentDidMount
方法:
instance.componentDidMount();
否则说明是更新阶段,调用componentDidUpdate
方法:
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
最后,state
更新完成之后,执行update
上的回调函数:
if (updateQueue !== null) {
commitUpdateQueue(finishedWork, updateQueue, instance);
}
export function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
instance: any,
): void {
const effects = finishedQueue.effects;
finishedQueue.effects = null;
if (effects !== null) {
// 依次执行已经update了的回调函数
for (let i = 0;i < effects.length;i++) {
const effect = effects[i];
const callback = effect.callback;
if (callback !== null) {
effect.callback = null;
callCallback(callback, instance);
}
}
}
}
最后
在更新完成之后,更改root.current
指向,相当于交换了current
和workInProgress
:
root.current = finishedWork;
然后再次进行调度:
ensureRootIsScheduled(root, now());
如果还有未完成的更新,即优先级不够的更新,那么这里会被继续调度进行更新。
如果当前更新中包含useEffect
,并且lanes
中含有同步lane
,那么需要立即执行flushPassiveEffect
:
if (
includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
root.tag !== LegacyRoot
) {
flushPassiveEffects();
}
相比于schedule
执行flushPassiveEffects
,这里执行更靠前。
最后还会执行flushSyncCallbacks
:
flushSyncCallbacks();
该函数用于将立即执行layout
里新添加的任务。