React源码 commit阶段详解

当render阶段完成后,意味着在内存中构建的workInProgress树所有更新工作已经完成,这包括树中fiber节点的更新、diff、effectTag的标记、effectList的收集。此时workInProgress树的完整形态如下:
和current树相比,它们的结构上固然存在区别,变化的fiber节点也存在于workInProgress树,但要将这些节点应用到DOM上却不会循环整棵树,而是通过循环effectList这个链表来实现,这样保证了只针对有变化的节点做工作。
所以循环effectList链表去将有更新的fiber节点应用到页面上是commit阶段的主要工作。
commit阶段的入口是commitRoot函数,它会告知scheduler以立即执行的优先级去调度commit阶段的工作。
function commitRoot(root) {
const renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority(
ImmediateSchedulerPriority,
commitRootImpl.bind(null, root, renderPriorityLevel),
);
return null;
}
scheduler去调度的是commitRootImpl,它是commit阶段的核心实现,整个commit阶段被划分成三个部分。
commit流程概览
commit阶段主要是针对root上收集的effectList进行处理。在真正的工作开始之前,有一个准备阶段,主要是变量的赋值,以及将root的effect加入到effectList中。随后开始针对effectList分三个阶段进行工作:
• before mutation:读取组件变更前的状态,针对类组件,调用getSnapshotBeforeUpdate,让我们可以在DOM变更前获取组件实例的信息;针对函数组件,异步调度useEffect。
• mutation:针对HostComponent,进行相应的DOM操作;针对类组件,调用componentWillUnmount;针对函数组件,执行useLayoutEffect的销毁函数。
• layout:在DOM操作完成后,读取组件的状态,针对类组件,调用生命周期componentDidMount和componentDidUpdate,调用setState的回调;针对函数组件填充useEffect 的 effect执行数组,并调度useEffect
before mutation和layout针对函数组件的useEffect调度是互斥的,只能发起一次调度
workInProgress 树切换到current树的时机是在mutation结束后,layout开始前。这样做的原因是在mutation阶段调用类组件的componentWillUnmount的时候,
还可以获取到卸载前的组件信息;在layout阶段调用componentDidMount/Update时,获取的组件信息更新后的。
function commitRootImpl(root, renderPriorityLevel) {

// 进入commit阶段,先执行一次之前未执行的useEffect
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);

// 准备阶段-----------------------------------------------

const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
if (finishedWork === null) {
return null;
}
root.finishedWork = null;
root.finishedLanes = NoLanes;

root.callbackNode = null;
root.callbackId = NoLanes;

// effectList的整理,将root上的effect连到effectList的末尾
let firstEffect;
if (finishedWork.effectTag > PerformedWork) {
if (finishedWork.lastEffect !== null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
// There is no effect on the root.
firstEffect = finishedWork.firstEffect;
}

// 准备阶段结束,开始处理effectList
if (firstEffect !== null) {

...

// before mutation阶段--------------------------------
nextEffect = firstEffect;
do {...} while (nextEffect !== null);

...

// mutation阶段---------------------------------------
nextEffect = firstEffect;
do {...} while (nextEffect !== null);

// 将wprkInProgress树切换为current树
root.current = finishedWork;

// layout阶段-----------------------------------------
nextEffect = firstEffect;
do {...} while (nextEffect !== null);

nextEffect = null;

// 通知浏览器去绘制
requestPaint();

} else {
// 没有effectList,直接将wprkInProgress树切换为current树
root.current = finishedWork;

}

const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

// 获取尚未处理的优先级,比如之前被跳过的任务的优先级
remainingLanes = root.pendingLanes;
// 将被跳过的优先级放到root上的pendingLanes(待处理的优先级)上
markRootFinished(root, remainingLanes);

/*

  • 每次commit阶段完成后,再执行一遍ensureRootIsScheduled,确保是否还有任务需要被调度。
  • 例如,高优先级插队的更新完成后,commit完成后,还会再执行一遍,保证之前跳过的低优先级任务
  • 重新调度
  • */
    ensureRootIsScheduled(root, now());

return null;
}
下面的部分,是对这三个阶段分别进行的详细讲解。
before Mutation
beforeMutation阶段的入口函数是commitBeforeMutationEffects
nextEffect = firstEffect;
do {
try {
commitBeforeMutationEffects();
} catch (error) {

}
} while (nextEffect !== null);
它的作用主要是调用类组件的getSnapshotBeforeUpdate,针对流量交易函数组件,异步调度useEffect。
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
const current = nextEffect.alternate;

...

const flags = nextEffect.flags;
if ((flags & Snapshot) !== NoFlags) {
  // 通过commitBeforeMutationEffectOnFiber调用getSnapshotBeforeUpdate
  commitBeforeMutationEffectOnFiber(current, nextEffect);
}

if ((flags & Passive) !== NoFlags) {
  // 异步调度useEffect
  if (!rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = true;
    scheduleCallback(NormalSchedulerPriority, () => {
      flushPassiveEffects();
      return null;
    });
  }
}
nextEffect = nextEffect.nextEffect;

}
}
commitBeforeMutationEffectOnFiber代码如下
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {

case ClassComponent: {
if (finishedWork.flags & Snapshot) {
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,
);
// 将返回值存储在内部属性上,方便componentDidUpdate获取
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
}
return;
}

}

}
mutation
mutation阶段会真正操作DOM节点,涉及到的操作有增、删、改。入口函数是commitMutationEffects
nextEffect = firstEffect;
do {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {

nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
由于过程较为复杂,所以我写了三篇文章来说明这三种DOM操作,如果想要了解细节,可以看一下。文章写于17还未正式发布的时候,所以里面的源码版本取自17.0.0-alpha0。
layout阶段
layout阶段的入口函数是commitLayoutEffects。
nextEffect = firstEffect;
do {
try {
commitLayoutEffects(root, lanes);
} catch (error) {

nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
我们只关注classComponent和functionComponent。针对前者,调用生命周期componentDidMount和componentDidUpdate,调用setState的回调;针对后者,填充useEffect 的 effect执行数组,并调度useEffect。
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
// 执行useLayoutEffect的创建
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);

  // 填充useEffect的effect执行数组
  schedulePassiveEffects(finishedWork);
  return;
}
case ClassComponent: {
  const instance = finishedWork.stateNode;
  if (finishedWork.flags & Update) {
    if (current === null) {
      // 如果是初始挂载阶段,调用componentDidMount
      instance.componentDidMount();
    } else {
      // 如果是更新阶段,调用componentDidUpdate
      const prevProps =
        finishedWork.elementType === finishedWork.type
          ? current.memoizedProps
          : resolveDefaultProps(finishedWork.type, current.memoizedProps);
      const prevState = current.memoizedState;

      instance.componentDidUpdate(
        prevProps,
        prevState,
        // 将getSnapshotBeforeUpdate的结果传入
        instance.__reactInternalSnapshotBeforeUpdate,
      );
    }
  }

  // 调用setState的回调
  const updateQueue: UpdateQueue<
    *,
  > | null = (finishedWork.updateQueue: any);
  if (updateQueue !== null) {

    commitUpdateQueue(finishedWork, updateQueue, instance);
  }
  return;
}

...

}
}
总结
commit阶段将effectList的处理分成三个阶段保证了不同生命周期函数的适时调用。相对于同步执行的useEffectLayout,useEffect的异步调度提供了一种不阻塞页面渲染的副作用操作入口。另外,标记root上还未处理的优先级和调用ensureRootIsScheduled使得被跳过的低优先级任务得以再次被调度。commit阶段的完成,也就意味着本次更新已经结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值