You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function commitRoot(root, finishedWork) {
isWorking = true;
isCommitting$1 = true;
startCommitTimer();
!(root.current !== finishedWork) ? invariant(false, 'Cannot commit the same tree as before. This is probably a bug related to the return field. This error is likely caused by a bug in React. Please file an issue.') : void 0;
var committedExpirationTime = root.pendingCommitExpirationTime;
!(committedExpirationTime !== NoWork) ? invariant(false, 'Cannot commit an incomplete root. This error is likely caused by a bug in React. Please file an issue.') : void 0;
root.pendingCommitExpirationTime = NoWork;
// Update the pending priority levels to account for the work that we are
// about to commit. This needs to happen before calling the lifecycles, since
// they may schedule additional updates.
var updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
var childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
var earliestRemainingTimeBeforeCommit = childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit ? childExpirationTimeBeforeCommit : updateExpirationTimeBeforeCommit;
markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);
var prevInteractions = null;
if (enableSchedulerTracing) {
// Restore any pending interactions at this point,
// So that cascading work triggered during the render phase will be accounted for.
prevInteractions = __interactionsRef.current;
__interactionsRef.current = root.memoizedInteractions;
}
// Reset this to null before calling lifecycles
ReactCurrentOwner$2.current = null;
var firstEffect = void 0;
if (finishedWork.effectTag > PerformedWork) {
// A fiber's effect list consists only of its children, not itself. So if
// the root has an effect, we need to add it to the end of the list. The
// resulting list is the set that would belong to the root's parent, if
// it had one; that is, all the effects in the tree including the root.
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;
}
prepareForCommit(root.containerInfo);
// Invoke instances of getSnapshotBeforeUpdate before mutation.
nextEffect = firstEffect;
startCommitSnapshotEffectsTimer();
while (nextEffect !== null) {
var didError = false;
var error = void 0;
{
invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
}
if (didError) {
!(nextEffect !== null) ? invariant(false, 'Should have next effect. This error is likely caused by a bug in React. Please file an issue.') : void 0;
captureCommitPhaseError(nextEffect, error);
// Clean-up
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
stopCommitSnapshotEffectsTimer();
if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this batch.
// This enables them to be grouped later.
recordCommitTime();
}
// Commit all the side-effects within a tree. We'll do this in two passes.
// The first pass performs all the host insertions, updates, deletions and
// ref unmounts.
nextEffect = firstEffect;
startCommitHostEffectsTimer();
while (nextEffect !== null) {
var _didError = false;
var _error = void 0;
{
invokeGuardedCallback(null, commitAllHostEffects, null);
if (hasCaughtError()) {
_didError = true;
_error = clearCaughtError();
}
}
if (_didError) {
!(nextEffect !== null) ? invariant(false, 'Should have next effect. This error is likely caused by a bug in React. Please file an issue.') : void 0;
captureCommitPhaseError(nextEffect, _error);
// Clean-up
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
stopCommitHostEffectsTimer();
resetAfterCommit(root.containerInfo);
// The work-in-progress tree is now the current tree. This must come after
// the first pass of the commit phase, so that the previous tree is still
// current during componentWillUnmount, but before the second pass, so that
// the finished work is current during componentDidMount/Update.
root.current = finishedWork;
// In the second pass we'll perform all life-cycles and ref callbacks.
// Life-cycles happen as a separate pass so that all placements, updates,
// and deletions in the entire tree have already been invoked.
// This pass also triggers any renderer-specific initial effects.
nextEffect = firstEffect;
startCommitLifeCyclesTimer();
while (nextEffect !== null) {
var _didError2 = false;
var _error2 = void 0;
{
invokeGuardedCallback(null, commitAllLifeCycles, null, root, committedExpirationTime);
if (hasCaughtError()) {
_didError2 = true;
_error2 = clearCaughtError();
}
}
if (_didError2) {
!(nextEffect !== null) ? invariant(false, 'Should have next effect. This error is likely caused by a bug in React. Please file an issue.') : void 0;
captureCommitPhaseError(nextEffect, _error2);
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
if (firstEffect !== null && rootWithPendingPassiveEffects !== null) {
// This commit included a passive effect. These do not need to fire until
// after the next paint. Schedule an callback to fire them in an async
// event. To ensure serial execution, the callback will be flushed early if
// we enter rootWithPendingPassiveEffects commit phase before then.
var callback = commitPassiveEffects.bind(null, root, firstEffect);
if (enableSchedulerTracing) {
// TODO: Avoid this extra callback by mutating the tracing ref directly,
// like we do at the beginning of commitRoot. I've opted not to do that
// here because that code is still in flux.
callback = unstable_wrap(callback);
}
passiveEffectCallbackHandle = unstable_runWithPriority(unstable_NormalPriority, function () {
return schedulePassiveEffects(callback);
});
passiveEffectCallback = callback;
}
isCommitting$1 = false;
isWorking = false;
stopCommitLifeCyclesTimer();
stopCommitTimer();
onCommitRoot(finishedWork.stateNode);
if (true && ReactFiberInstrumentation_1.debugTool) {
ReactFiberInstrumentation_1.debugTool.onCommitWork(finishedWork);
}
var updateExpirationTimeAfterCommit = finishedWork.expirationTime;
var childExpirationTimeAfterCommit = finishedWork.childExpirationTime;
var earliestRemainingTimeAfterCommit = childExpirationTimeAfterCommit > updateExpirationTimeAfterCommit ? childExpirationTimeAfterCommit : updateExpirationTimeAfterCommit;
if (earliestRemainingTimeAfterCommit === NoWork) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
}
onCommit(root, earliestRemainingTimeAfterCommit);
if (enableSchedulerTracing) {
__interactionsRef.current = prevInteractions;
var subscriber = void 0;
try {
subscriber = __subscriberRef.current;
if (subscriber !== null && root.memoizedInteractions.size > 0) {
var threadID = computeThreadID(committedExpirationTime, root.interactionThreadID);
subscriber.onWorkStopped(root.memoizedInteractions, threadID);
}
} catch (error) {
// It's not safe for commitRoot() to throw.
// Store the error for now and we'll re-throw in finishRendering().
if (!hasUnhandledError) {
hasUnhandledError = true;
unhandledError = error;
}
} finally {
// Clear completed interactions from the pending Map.
// Unless the render was suspended or cascading work was scheduled,
// In which case– leave pending interactions until the subsequent render.
var pendingInteractionMap = root.pendingInteractionMap;
pendingInteractionMap.forEach(function (scheduledInteractions, scheduledExpirationTime) {
// Only decrement the pending interaction count if we're done.
// If there's still work at the current priority,
// That indicates that we are waiting for suspense data.
if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
pendingInteractionMap.delete(scheduledExpirationTime);
scheduledInteractions.forEach(function (interaction) {
interaction.__count--;
if (subscriber !== null && interaction.__count === 0) {
try {
subscriber.onInteractionScheduledWorkCompleted(interaction);
} catch (error) {
// It's not safe for commitRoot() to throw.
// Store the error for now and we'll re-throw in finishRendering().
if (!hasUnhandledError) {
hasUnhandledError = true;
unhandledError = error;
}
}
}
});
}
});
}
}
}
commit 函数过程如下:
如果 finishedWork 有 effect,加入 effect 链表中。
遍历 effect 链,更新 class 组件实例上的 state、props,通过函数 commitBeforeMutationLifecycles 执行 getSnapshotBeforeUpdate 等生命周期函数。
遍历 effect 链,通过 commitAllHostEffects 函数对真实的 dom 进行插入、更新、删除。
function commitBeforeMutationLifeCycles(current$$1, finishedWork) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
commitHookEffectList(UnmountSnapshot, NoEffect$1, finishedWork);
return;
}
case ClassComponent:
{
if (finishedWork.effectTag & Snapshot) {
if (current$$1 !== null) {
var prevProps = current$$1.memoizedProps;
var prevState = current$$1.memoizedState;
startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
var instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
!(instance.props === finishedWork.memoizedProps) ? warning$1(false, 'Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
!(instance.state === finishedWork.memoizedState) ? warning$1(false, 'Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
}
}
var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState);
{
var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
warningWithoutStack$1(false, '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentName(finishedWork.type));
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
stopPhaseTimer();
}
}
return;
}
case HostRoot:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
return;
default:
{
invariant(false, 'This unit of work tag should not have side-effects. This error is likely caused by a bug in React. Please file an issue.');
}
}
}
在后一个 commitBeforeMutationLifeCycles 函数中会根据 tag 不同分别处理。对于大部分 tag 都选择不处理,对于 ClassComponent,会执行 getSnapshotBeforeUpdate 生命周期函数。
第二次遍历 effect 链,执行 commitAllHostEffects 函数
function commitAllHostEffects() {
while (nextEffect !== null) {
{
setCurrentFiber(nextEffect);
}
recordEffect();
var effectTag = nextEffect.effectTag;
if (effectTag & ContentReset) {
commitResetTextContent(nextEffect);
}
if (effectTag & Ref) {
var current$$1 = nextEffect.alternate;
if (current$$1 !== null) {
commitDetachRef(current$$1);
}
}
// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every
// possible bitmap value, we remove the secondary effects from the
// effect tag and switch on that value.
var primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
case Placement:
{
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted
// does and isMounted is deprecated anyway so we should be able
// to kill this.
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate:
{
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
nextEffect.effectTag &= ~Placement;
// Update
var _current = nextEffect.alternate;
commitWork(_current, nextEffect);
break;
}
case Update:
{
var _current2 = nextEffect.alternate;
commitWork(_current2, nextEffect);
break;
}
case Deletion:
{
commitDeletion(nextEffect);
break;
}
}
nextEffect = nextEffect.nextEffect;
}
{
resetCurrentFiber();
}
}
这次遍历 effect 链是对真实 dom 节点操作:文本重置、插入、更新、删除等。
插入节点:commitPlacement
更新节点:commitWork
删除节点:commitDeletion
第三次遍历 effect 链,执行 commitAllLifeCycles:
function commitAllLifeCycles(finishedRoot, committedExpirationTime) {
{
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
ReactStrictModeWarnings.flushLegacyContextWarning();
if (warnAboutDeprecatedLifecycles) {
ReactStrictModeWarnings.flushPendingDeprecationWarnings();
}
}
while (nextEffect !== null) {
{
setCurrentFiber(nextEffect);
}
var effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
recordEffect();
var current$$1 = nextEffect.alternate;
commitLifeCycles(finishedRoot, current$$1, nextEffect, committedExpirationTime);
}
if (effectTag & Ref) {
recordEffect();
commitAttachRef(nextEffect);
}
if (effectTag & Passive) {
rootWithPendingPassiveEffects = finishedRoot;
}
nextEffect = nextEffect.nextEffect;
}
{
resetCurrentFiber();
}
}
在函数 commitAllLifeCycles 中调用 commitLifeCycles:
function commitLifeCycles(finishedRoot, current$$1, finishedWork, committedExpirationTime) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
break;
}
case ClassComponent:
{
var instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current$$1 === null) {
startPhaseTimer(finishedWork, 'componentDidMount');
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
!(instance.props === finishedWork.memoizedProps) ? warning$1(false, 'Expected %s props to match memoized props before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
!(instance.state === finishedWork.memoizedState) ? warning$1(false, 'Expected %s state to match memoized state before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
}
}
instance.componentDidMount();
stopPhaseTimer();
} else {
var prevProps = finishedWork.elementType === finishedWork.type ? current$$1.memoizedProps : resolveDefaultProps(finishedWork.type, current$$1.memoizedProps);
var prevState = current$$1.memoizedState;
startPhaseTimer(finishedWork, 'componentDidUpdate');
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
!(instance.props === finishedWork.memoizedProps) ? warning$1(false, 'Expected %s props to match memoized props before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
!(instance.state === finishedWork.memoizedState) ? warning$1(false, 'Expected %s state to match memoized state before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
}
}
instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
stopPhaseTimer();
}
}
var updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
!(instance.props === finishedWork.memoizedProps) ? warning$1(false, 'Expected %s props to match memoized props before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
!(instance.state === finishedWork.memoizedState) ? warning$1(false, 'Expected %s state to match memoized state before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance') : void 0;
}
}
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
commitUpdateQueue(finishedWork, updateQueue, instance, committedExpirationTime);
}
return;
}
case HostRoot:
{
var _updateQueue = finishedWork.updateQueue;
if (_updateQueue !== null) {
var _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(finishedWork, _updateQueue, _instance, committedExpirationTime);
}
return;
}
case HostComponent:
{
var _instance2 = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current$$1 === null && finishedWork.effectTag & Update) {
var type = finishedWork.type;
var props = finishedWork.memoizedProps;
commitMount(_instance2, type, props, finishedWork);
}
return;
}
case HostText:
{
// We have no life-cycles associated with text.
return;
}
case HostPortal:
{
// We have no life-cycles associated with portals.
return;
}
case Profiler:
{
if (enableProfilerTimer) {
var onRender = finishedWork.memoizedProps.onRender;
if (enableSchedulerTracing) {
onRender(finishedWork.memoizedProps.id, current$$1 === null ? 'mount' : 'update', finishedWork.actualDuration, finishedWork.treeBaseDuration, finishedWork.actualStartTime, getCommitTime(), finishedRoot.memoizedInteractions);
} else {
onRender(finishedWork.memoizedProps.id, current$$1 === null ? 'mount' : 'update', finishedWork.actualDuration, finishedWork.treeBaseDuration, finishedWork.actualStartTime, getCommitTime());
}
}
return;
}
case SuspenseComponent:
break;
case IncompleteClassComponent:
break;
default:
{
invariant(false, 'This unit of work tag should not have side-effects. This error is likely caused by a bug in React. Please file an issue.');
}
}
}
React 源码解析:commit 流程
在上一篇介绍了 render 流程,这里将介绍 commit 流程。render 做了很多工作,所以 commit 相对于 render 简单许多。
在函数 performWorkOnRoot 中调用完 renderRoot 函数后,会进入 completeRoot 函数。completeRoot 函数最重要的是调用 commitRoot 函数。
commit 函数过程如下:
在第一次遍历的时候,执行 commitBeforeMutationLifecycles 函数:
commitBeforeMutationLifecycles 函数中调用 commitBeforeMutationLifeCycles 函数,两个函数名字一样,但是函数内容不一样,后一个 commitBeforeMutationLifeCycles 函数是:
在后一个 commitBeforeMutationLifeCycles 函数中会根据 tag 不同分别处理。对于大部分 tag 都选择不处理,对于 ClassComponent,会执行 getSnapshotBeforeUpdate 生命周期函数。
第二次遍历 effect 链,执行 commitAllHostEffects 函数
这次遍历 effect 链是对真实 dom 节点操作:文本重置、插入、更新、删除等。
第三次遍历 effect 链,执行 commitAllLifeCycles:
在函数 commitAllLifeCycles 中调用 commitLifeCycles:
commitLifeCycles 针对 ClassComponent 主要执行下面步骤:
其他 tag 大部分不处理。
最后 commitRoot 函数做一些清理工作:
commit 流程结束。
The text was updated successfully, but these errors were encountered: