上一篇,我们了解到beginWork和completeWork阶段,到这里为止,协调的数据都在内存中,页面上还没有任何变化。接下来的commit阶段至关重要,接下来我们一探究竟吧~
一. commitRoot
function commitRoot(root) {
var renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority$1(ImmediatePriority$1, commitRootImpl.bind(null, root, renderPriorityLevel));
return null;
}
getCurrentPriorityLevel是优先级有关,这里涉及到schedule调度内部优先级问题。这块,我们再后续的schedule阶段系统性分享。
二. commitRootImpl
rootWithPendingPassiveEffects
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
// ...
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
}
// ...
onCommitRoot(finishedWork.stateNode, renderPriorityLevel);
// ...
ensureRootIsScheduled(root, now());
// ...
flushSyncCallbackQueue();
}
rootWithPendingPassiveEffects是rootFiber对应的effects,在执行commit核心流程之前,先flushPassiveEffects,简单来说,可能存在后续的update,但rootFiber上存在effects,因为flushPassiveEffects是异步宏任务执行的,并不会同步在commit中处理掉。并且异步宏任务队列推入schedule的taskQueue里面,消息队列的执行也并非是按照队列顺序执行。
firstEffect
var firstEffect;
if (finishedWork.flags > 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;
}
firstEffect的判断,如果是flags大于PerformedWork,即存在副作用。这里第一个firstEffect就是rootFiber上的firstEffect(在beginWork阶段生成的)
下面是commit的核心三阶段
三. commit第一阶段
if (firstEffect !== null) {
// ...
nextEffect = firstEffect;
do {
{
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
if (hasCaughtError()) {
if (!(nextEffect !== null)) {
{
throw Error( "Should be working on an effect." );
}
}
var error = clearCaughtError();
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
可以到看到,遍历所有的effect调用commitBeforeMutationEffects
commitBeforeMutationEffects
while (nextEffect !== null) {
var current = nextEffect.alternate;
var flags = nextEffect.flags;
// ...
if ((flags & Snapshot) !== NoFlags) {
setCurrentFiber(nextEffect);
commitBeforeMutationLifeCycles(current, nextEffect);
resetCurrentFiber();
}
// ...
if ((flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalPriority$1, function () {
flushPassiveEffects();
return null;
});
}
}
nextEffect = nextEffect.nextEffect;
}
这是Snapshot其实是针对classComponent的生命周期调用,我们重点研究function Component,这里先忽略。感兴趣的朋友可以给我留言,我会针对classCompoent单独聊聊。
rootDoesHavePassiveEffects初始化是空,是标志着rootFiber上是否有passive effect。除了这里,后续的commit三阶段也会使用它。
scheduleCallback相对复杂,从这里开始,开始涉及schedule核心,这块我们在后续schedule单独章节聊。这里,我们只需记住发起了异步任务,等待schedule调度。
这里先留个思考题🤔:函数组件的useEffect在什么时候执行的?
四. commit第二阶段
nextEffect = firstEffect;
do {
{
invokeGuardedCallback(null, commitMutationEffects, null, root, renderPriorityLevel);
if (hasCaughtError()) {
if (!(nextEffect !== null)) {
{
throw Error( "Should be working on an effect." );
}
}
var _error = clearCaughtError();
captureCommitPhaseError(nextEffect, _error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
经过commit第一阶段nextEffect变成了null,需要重置为firstEffect。继续第二次循环。
commitMutationEffects
function commitMutationEffects(root, renderPriorityLevel) {
while (nextEffect !== null) {
// ...
var primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
switch (primaryFlags) {
case Placement:
commitPlacement(nextEffect);
nextEffect.flags &= ~Placement;
break;
case PlacementAndUpdate:
commitPlacement(nextEffect);
nextEffect.flags &= ~Placement;
var _current = nextEffect.alternate;
commitWork(_current, nextEffect);
break;
// ...
case Update:
var _current3 = nextEffect.alternate;
commitWork(_current3, nextEffect);
break;
case Deletion:
commitDeletion(root, nextEffect);
break;
}
nextEffect = nextEffect.nextEffect;
}
}
上面虽然有许多case,本质上只需关注创建和更新。
子组件将进入Update,根函数组件将进入PlacementAndUpdate,最后rootFiber不会匹配任何case。
子组件Update Case,执行commitWork,本质上执行的commitHookEffectListUnmount,这里可能有人要问:初始化阶段,哪有unmount?确实是没有,commitHookEffectListUnmount方法会判断effect上是否有destroy,有则执行。那么effect.destory什么时候才会有值呢?答案是执行了effect.create方法之后,所以初始阶段即使也执行了commitHookEffectListUnmount,也没关系,因为effect.destory为undefined。
根函数组件,相比较普通子组件,多执行了commitPlacement方法,其核心的代码如下:
var child = node.child;
if (child !== null) {
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
var sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
sibling = sibling.sibling;
}
}
其实就是递归调用自己,最终都将进入appendChildToContainer方法调用,appendChildToContainer直接使用parentNode.appendChild(child)。到这里就很清晰了,在completeWork阶段,rootFiber上stateNode实例即整个根函数组件的DOM对象,在这里一把插入到DOM文档流中。其中child就是之前在completeWork阶段准备好的整个DOM树。
五. commit第三阶段
nextEffect = firstEffect;
do {
{
invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
if (hasCaughtError()) {
if (!(nextEffect !== null)) {
{
throw Error( "Should be working on an effect." );
}
}
var _error2 = clearCaughtError();
captureCommitPhaseError(nextEffect, _error2);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
nextEffect = null;
经过第二阶段循环后,nextEffect又变成了null,再次重置为firstEffect,继续第三次循环
commitLayoutEffects
第三阶段,其核心也是循环所有nextEffect链接,核心是执行commitLifeCycles方法。
if (flags & (Update | Callback)) {
var current = nextEffect.alternate;
commitLifeCycles(root, current, nextEffect);
}
commitLifeCycles
function commitLifeCycles(finishedRoot, current, finishedWork, committedLanes) {
switch (finishedWork.tag) {
case // ...
case Block:
commitHookEffectListMount();
schedulePassiveEffects();
return;
// ...
}
}
commitHookEffectListMount
function commitHookEffectListMount(tag, finishedWork) {
var updateQueue = finishedWork.updateQueue;
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
var firstEffect = lastEffect.next;
var effect = firstEffect;
do {
if ((effect.tag & tag) === tag) {
// Mount
var create = effect.create;
effect.destroy = create();
{
var destroy = effect.destroy;
if (destroy !== undefined && typeof destroy !== 'function') {
var addendum = void 0;
if (destroy === null) {
addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).';
} else if (typeof destroy.then === 'function') {
addendum = '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' + 'Instead, write the async function inside your effect ' + 'and call it immediately:\n\n' + 'useEffect(() => {\n' + ' async function fetchData() {\n' + ' // You can await here\n' + ' const response = await MyAPI.getData(someId);\n' + ' // ...\n' + ' }\n' + ' fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching';
} else {
addendum = ' You returned: ' + destroy;
}
error('An effect function must not return anything besides a function, ' + 'which is used for clean-up.%s', addendum);
}
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
这段代码也很简单,对于函数组件updateQueue是一个effect环状链表,执行tag=3,即useLayoutEffect回调。 这也意味着,这个阶段useLayoutEffect开始执行了,但要注意的是useEffect并没有执行,因为他的tag = 5。
schedulePassiveEffects
function schedulePassiveEffects(finishedWork) {
var updateQueue = finishedWork.updateQueue;
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
var firstEffect = lastEffect.next;
var effect = firstEffect;
do {
var _effect = effect,
next = _effect.next,
tag = _effect.tag;
if ((tag & Passive$1) !== NoFlags$1 && (tag & HasEffect) !== NoFlags$1) {
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}
这里也是如此,代码也很简单,对于函数组件,如果是Passive或HasEffect,执行。这里就过滤useLayoutEffect了,这里是useEffect进入。
enqueuePendingPassiveHookEffectUnmount
function enqueuePendingPassiveHookEffectUnmount(fiber, effect) {
pendingPassiveHookEffectsUnmount.push(effect, fiber);
{
fiber.flags |= PassiveUnmountPendingDev;
var alternate = fiber.alternate;
if (alternate !== null) {
alternate.flags |= PassiveUnmountPendingDev;
}
}
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalPriority$1, function () {
flushPassiveEffects();
return null;
});
}
}
enqueuePendingPassiveHookEffectMount
function enqueuePendingPassiveHookEffectMount(fiber, effect) {
pendingPassiveHookEffectsMount.push(effect, fiber);
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalPriority$1, function () {
flushPassiveEffects();
return null;
});
}
}
这里也很简单,将effect添加到pendingPassiveHookEffectsMount队列中。而rootDoesHavePassiveEffects在commit第一阶段被设置成了true,下面的调度不会执行。
好了,至此commit三阶段核心内容结束了。
那么问题来了:pendingPassiveHookEffectsMount这个东西有啥用?
这个东西非常重要,这个是react执行useEffect回调的任务队列。但为什么commit三阶段之后没有地方调用呢?
这待回到scheduleCallback异步宏任务里,回调中才会用到pendingPassiveHookEffectsMount。
下一章节,我们重点聊聊schedule
码字不易,多多点赞关注~