React 16.8.0 初次渲染 源码分析(三)
书接上文,从 scheduleWork 开始
scheduleWork 主要初始化 FiberRoot Time相关字段
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) { // fiber:FiberNode, expirationTime: Sync const root = scheduleWorkToRoot(fiber, expirationTime); // 返回FiberRoot if (root === null) { return; } if ( !isWorking && nextRenderExpirationTime !== NoWork && expirationTime > nextRenderExpirationTime ) { // This is an interruption. (Used for performance tracking.) interruptedBy = fiber; resetStack(); } markPendingPriorityLevel(root, expirationTime); if ( // If we're in the render phase, we don't need to schedule this root // for an update, because we'll do it before we exit... !isWorking || isCommitting || // ...unless this is a different root than the one we're rendering. nextRoot !== root ) { const rootExpirationTime = root.expirationTime; requestWork(root, rootExpirationTime); } if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { // Reset this back to zero so subsequent updates don't throw. nestedUpdateCount = 0; invariant( false, 'Maximum update depth exceeded. This can happen when a ' + 'component repeatedly calls setState inside ' + 'componentWillUpdate or componentDidUpdate. React limits ' + 'the number of nested updates to prevent infinite loops.', ); } }
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null { // fiber:FiberNode, expirationTime: Sync recordScheduleUpdate(); // Update the source fiber's expiration time if (fiber.expirationTime < expirationTime) { fiber.expirationTime = expirationTime; } let alternate = fiber.alternate; if (alternate !== null && alternate.expirationTime < expirationTime) { alternate.expirationTime = expirationTime; } // Walk the parent path to the root and update the child expiration time. let node = fiber.return; let root = null; if (node === null && fiber.tag === HostRoot) { root = fiber.stateNode; } else { while (node !== null) { alternate = node.alternate; if (node.childExpirationTime < expirationTime) { node.childExpirationTime = expirationTime; if ( alternate !== null && alternate.childExpirationTime < expirationTime ) { alternate.childExpirationTime = expirationTime; } } else if ( alternate !== null && alternate.childExpirationTime < expirationTime ) { alternate.childExpirationTime = expirationTime; } if (node.return === null && node.tag === HostRoot) { root = node.stateNode; break; } node = node.return; } } if (enableSchedulerTracing) { if (root !== null) { const interactions = __interactionsRef.current; if (interactions.size > 0) { const pendingInteractionMap = root.pendingInteractionMap; const pendingInteractions = pendingInteractionMap.get(expirationTime); if (pendingInteractions != null) { interactions.forEach(interaction => { if (!pendingInteractions.has(interaction)) { // Update the pending async work count for previously unscheduled interaction. interaction.__count++; } pendingInteractions.add(interaction); }); } else { pendingInteractionMap.set(expirationTime, new Set(interactions)); // Update the pending async work count for the current interactions. interactions.forEach(interaction => { interaction.__count++; }); } const subscriber = __subscriberRef.current; if (subscriber !== null) { const threadID = computeThreadID( expirationTime, root.interactionThreadID, ); subscriber.onWorkScheduled(interactions, threadID); } } } } return root; }
export function recordScheduleUpdate(): void { if (enableUserTimingAPI) { if (isCommitting) { hasScheduledUpdateInCurrentCommit = true; } if ( currentPhase !== null && currentPhase !== 'componentWillMount' && currentPhase !== 'componentWillReceiveProps' ) { hasScheduledUpdateInCurrentPhase = true; } } }
- packages/react-reconciler/src/ReactFiberScheduler.js
firstScheduledRoot = lastScheduledRoot = root;
// requestWork is called by the scheduler whenever a root receives an update. // It's up to the renderer to call renderRoot at some point in the future. function requestWork(root: FiberRoot, expirationTime: ExpirationTime) { //root: FiberRoot, expirationTime: Sync addRootToSchedule(root, expirationTime); if (isRendering) { // Prevent reentrancy. Remaining work will be scheduled at the end of // the currently rendering batch. return; } if (isBatchingUpdates) { // Flush work at the end of the batch. if (isUnbatchingUpdates) { // ...unless we're inside unbatchedUpdates, in which case we should // flush it now. nextFlushedRoot = root; nextFlushedExpirationTime = Sync; performWorkOnRoot(root, Sync, false); } return; } // TODO: Get rid of Sync and use current time? if (expirationTime === Sync) { performSyncWork(); } else { scheduleCallbackWithExpirationTime(root, expirationTime); } }
function performSyncWork() { performWork(Sync, false); }
function performWork(minExpirationTime: ExpirationTime, isYieldy: boolean) { // minExpirationTime: Sync, isYieldy: false // Keep working on roots until there's no more work, or until there's a higher // priority event. findHighestPriorityRoot(); if (isYieldy) { recomputeCurrentRendererTime(); currentSchedulerTime = currentRendererTime; if (enableUserTimingAPI) { const didExpire = nextFlushedExpirationTime > currentRendererTime; const timeout = expirationTimeToMs(nextFlushedExpirationTime); stopRequestCallbackTimer(didExpire, timeout); } while ( nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime && !(didYield && currentRendererTime > nextFlushedExpirationTime) ) { performWorkOnRoot( nextFlushedRoot, nextFlushedExpirationTime, currentRendererTime > nextFlushedExpirationTime, ); findHighestPriorityRoot(); recomputeCurrentRendererTime(); currentSchedulerTime = currentRendererTime; } } else { while ( nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime ) { performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false); findHighestPriorityRoot(); } } // We're done flushing work. Either we ran out of time in this callback, // or there's no more work left with sufficient priority. // If we're inside a callback, set this to false since we just completed it. if (isYieldy) { callbackExpirationTime = NoWork; callbackID = null; } // If there's work left over, schedule a new callback. if (nextFlushedExpirationTime !== NoWork) { scheduleCallbackWithExpirationTime( ((nextFlushedRoot: any): FiberRoot), nextFlushedExpirationTime, ); } // Clean-up. finishRendering(); }
function findHighestPriorityRoot() { let highestPriorityWork = NoWork; let highestPriorityRoot = null; if (lastScheduledRoot !== null) { let previousScheduledRoot = lastScheduledRoot; let root = firstScheduledRoot; while (root !== null) { const remainingExpirationTime = root.expirationTime; if (remainingExpirationTime === NoWork) { // This root no longer has work. Remove it from the scheduler. // TODO: This check is redudant, but Flow is confused by the branch // below where we set lastScheduledRoot to null, even though we break // from the loop right after. invariant( previousScheduledRoot !== null && lastScheduledRoot !== null, 'Should have a previous and last root. This error is likely ' + 'caused by a bug in React. Please file an issue.', ); if (root === root.nextScheduledRoot) { // This is the only root in the list. root.nextScheduledRoot = null; firstScheduledRoot = lastScheduledRoot = null; break; } else if (root === firstScheduledRoot) { // This is the first root in the list. const next = root.nextScheduledRoot; firstScheduledRoot = next; lastScheduledRoot.nextScheduledRoot = next; root.nextScheduledRoot = null; } else if (root === lastScheduledRoot) { // This is the last root in the list. lastScheduledRoot = previousScheduledRoot; lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; root.nextScheduledRoot = null; break; } else { previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot; root.nextScheduledRoot = null; } root = previousScheduledRoot.nextScheduledRoot; } else { if (remainingExpirationTime > highestPriorityWork) { // Update the priority, if it's higher highestPriorityWork = remainingExpirationTime; highestPriorityRoot = root; } if (root === lastScheduledRoot) { break; } if (highestPriorityWork === Sync) { // Sync is highest priority by definition so // we can stop searching. break; } previousScheduledRoot = root; root = root.nextScheduledRoot; } } } nextFlushedRoot = highestPriorityRoot; nextFlushedExpirationTime = highestPriorityWork; }
nextFlushedRoot = highestPriorityRoot; // FiberRoot
nextFlushedExpirationTime = highestPriorityWork; // Sync
performWork方法循环 查找需要优先执行Work的Root,然后执行 performWorkOnRoot
没有需要执行Work的Root时,或者 执行Work的时间 大于 下一次渲染的过期时间 退出循环。
function performWorkOnRoot( root: FiberRoot, // FiberRoot expirationTime: ExpirationTime, // Sync isYieldy: boolean, // false ) { invariant( !isRendering, 'performWorkOnRoot was called recursively. This error is likely caused ' + 'by a bug in React. Please file an issue.', ); isRendering = true; // Check if this is async work or sync/expired work. if (!isYieldy) { // Flush work without yielding. // TODO: Non-yieldy work does not necessarily imply expired work. A renderer // may want to perform some work without yielding, but also without // requiring the root to complete (by triggering placeholders). // root.finishedWork = null let finishedWork = root.finishedWork; if (finishedWork !== null) { // This root is already complete. We can commit it. completeRoot(root, finishedWork, expirationTime); } else { root.finishedWork = null; // If this root previously suspended, clear its existing timeout, since // we're about to try rendering again. // root.timeoutHandle = -1 const timeoutHandle = root.timeoutHandle; if (timeoutHandle !== noTimeout) { root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above cancelTimeout(timeoutHandle); } // 渲染 Root renderRoot(root, isYieldy); finishedWork = root.finishedWork; if (finishedWork !== null) { // We've completed the root. Commit it. completeRoot(root, finishedWork, expirationTime); } } } else { // Flush async work. let finishedWork = root.finishedWork; if (finishedWork !== null) { // This root is already complete. We can commit it. completeRoot(root, finishedWork, expirationTime); } else { root.finishedWork = null; // If this root previously suspended, clear its existing timeout, since // we're about to try rendering again. const timeoutHandle = root.timeoutHandle; if (timeoutHandle !== noTimeout) { root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above cancelTimeout(timeoutHandle); } renderRoot(root, isYieldy); finishedWork = root.finishedWork; if (finishedWork !== null) { // We've completed the root. Check the if we should yield one more time // before committing. if (!shouldYieldToRenderer()) { // Still time left. Commit the root. completeRoot(root, finishedWork, expirationTime); } else { // There's no time left. Mark this root as complete. We'll come // back and commit it later. root.finishedWork = finishedWork; } } } } isRendering = false; }
renderRoot 是一个300行的恐怖方法。
重置了Stack
nextRoot = null; nextRenderExpirationTime = NoWork; nextLatestAbsoluteTimeoutMs = -1; nextRenderDidError = false; nextUnitOfWork = null;
创建当前循环的WIP
resetStack();
nextRoot = root; nextRenderExpirationTime = expirationTime; nextUnitOfWork = createWorkInProgress( nextRoot.current, null, nextRenderExpirationTime, );
执行一次 workLoop
function renderRoot(root: FiberRoot, isYieldy: boolean): void { invariant( !isWorking, 'renderRoot was called recursively. This error is likely caused ' + 'by a bug in React. Please file an issue.', ); flushPassiveEffects(); isWorking = true; const previousDispatcher = ReactCurrentDispatcher.current; ReactCurrentDispatcher.current = ContextOnlyDispatcher; const expirationTime = root.nextExpirationTimeToWorkOn; // Check if we're starting from a fresh stack, or if we're resuming from // previously yielded work. if ( expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null ) { // Reset the stack and start working from the root. resetStack(); nextRoot = root; nextRenderExpirationTime = expirationTime; nextUnitOfWork = createWorkInProgress( nextRoot.current, null, nextRenderExpirationTime, ); root.pendingCommitExpirationTime = NoWork; if (enableSchedulerTracing) { // Determine which interactions this batch of work currently includes, // So that we can accurately attribute time spent working on it, // And so that cascading work triggered during the render phase will be associated with it. const interactions: Set<Interaction> = new Set(); root.pendingInteractionMap.forEach( (scheduledInteractions, scheduledExpirationTime) => { if (scheduledExpirationTime >= expirationTime) { scheduledInteractions.forEach(interaction => interactions.add(interaction), ); } }, ); // Store the current set of interactions on the FiberRoot for a few reasons: // We can re-use it in hot functions like renderRoot() without having to recalculate it. // We will also use it in commitWork() to pass to any Profiler onRender() hooks. // This also provides DevTools with a way to access it when the onCommitRoot() hook is called. root.memoizedInteractions = interactions; if (interactions.size > 0) { const subscriber = __subscriberRef.current; if (subscriber !== null) { const threadID = computeThreadID( expirationTime, root.interactionThreadID, ); try { subscriber.onWorkStarted(interactions, threadID); } catch (error) { // Work thrown by an interaction tracing subscriber should be rethrown, // But only once it's safe (to avoid leaving the scheduler in an invalid state). // Store the error for now and we'll re-throw in finishRendering(). if (!hasUnhandledError) { hasUnhandledError = true; unhandledError = error; } } } } } } let prevInteractions: Set<Interaction> = (null: any); if (enableSchedulerTracing) { // We're about to start new traced work. // Restore pending interactions so cascading work triggered during the render phase will be accounted for. prevInteractions = __interactionsRef.current; __interactionsRef.current = root.memoizedInteractions; } let didFatal = false; startWorkLoopTimer(nextUnitOfWork); do { try { workLoop(isYieldy); } catch (thrownValue) { resetContextDependences(); resetHooks(); // Reset in case completion throws. // This is only used in DEV and when replaying is on. let mayReplay; if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) { mayReplay = mayReplayFailedUnitOfWork; mayReplayFailedUnitOfWork = true; } if (nextUnitOfWork === null) { // This is a fatal error. didFatal = true; onUncaughtError(thrownValue); } else { if (enableProfilerTimer && nextUnitOfWork.mode & ProfileMode) { // Record the time spent rendering before an error was thrown. // This avoids inaccurate Profiler durations in the case of a suspended render. stopProfilerTimerIfRunningAndRecordDelta(nextUnitOfWork, true); } if (__DEV__) { // Reset global debug state // We assume this is defined in DEV (resetCurrentlyProcessingQueue: any)(); } if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) { if (mayReplay) { const failedUnitOfWork: Fiber = nextUnitOfWork; replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy); } } // TODO: we already know this isn't true in some cases. // At least this shows a nicer error message until we figure out the cause. // https://github.com/facebook/react/issues/12449#issuecomment-386727431 invariant( nextUnitOfWork !== null, 'Failed to replay rendering after an error. This ' + 'is likely caused by a bug in React. Please file an issue ' + 'with a reproducing case to help us find it.', ); const sourceFiber: Fiber = nextUnitOfWork; let returnFiber = sourceFiber.return; if (returnFiber === null) { // This is the root. The root could capture its own errors. However, // we don't know if it errors before or after we pushed the host // context. This information is needed to avoid a stack mismatch. // Because we're not sure, treat this as a fatal error. We could track // which phase it fails in, but doesn't seem worth it. At least // for now. didFatal = true; onUncaughtError(thrownValue); } else { throwException( root, returnFiber, sourceFiber, thrownValue, nextRenderExpirationTime, ); nextUnitOfWork = completeUnitOfWork(sourceFiber); continue; } } } break; } while (true); if (enableSchedulerTracing) { // Traced work is done for now; restore the previous interactions. __interactionsRef.current = prevInteractions; } // We're done performing work. Time to clean up. isWorking = false; ReactCurrentDispatcher.current = previousDispatcher; resetContextDependences(); resetHooks(); // Yield back to main thread. if (didFatal) { const didCompleteRoot = false; stopWorkLoopTimer(interruptedBy, didCompleteRoot); interruptedBy = null; // There was a fatal error. if (__DEV__) { resetStackAfterFatalErrorInDev(); } // `nextRoot` points to the in-progress root. A non-null value indicates // that we're in the middle of an async render. Set it to null to indicate // there's no more work to be done in the current batch. nextRoot = null; onFatal(root); return; } if (nextUnitOfWork !== null) { // There's still remaining async work in this tree, but we ran out of time // in the current frame. Yield back to the renderer. Unless we're // interrupted by a higher priority update, we'll continue later from where // we left off. const didCompleteRoot = false; stopWorkLoopTimer(interruptedBy, didCompleteRoot); interruptedBy = null; onYield(root); return; } // We completed the whole tree. const didCompleteRoot = true; stopWorkLoopTimer(interruptedBy, didCompleteRoot); const rootWorkInProgress = root.current.alternate; invariant( rootWorkInProgress !== null, 'Finished root should have a work-in-progress. This error is likely ' + 'caused by a bug in React. Please file an issue.', ); // `nextRoot` points to the in-progress root. A non-null value indicates // that we're in the middle of an async render. Set it to null to indicate // there's no more work to be done in the current batch. nextRoot = null; interruptedBy = null; if (nextRenderDidError) { // There was an error if (hasLowerPriorityWork(root, expirationTime)) { // There's lower priority work. If so, it may have the effect of fixing // the exception that was just thrown. Exit without committing. This is // similar to a suspend, but without a timeout because we're not waiting // for a promise to resolve. React will restart at the lower // priority level. markSuspendedPriorityLevel(root, expirationTime); const suspendedExpirationTime = expirationTime; const rootExpirationTime = root.expirationTime; onSuspend( root, rootWorkInProgress, suspendedExpirationTime, rootExpirationTime, -1, // Indicates no timeout ); return; } else if ( // There's no lower priority work, but we're rendering asynchronously. // Synchronsouly attempt to render the same level one more time. This is // similar to a suspend, but without a timeout because we're not waiting // for a promise to resolve. !root.didError && isYieldy ) { root.didError = true; const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime); const rootExpirationTime = (root.expirationTime = Sync); onSuspend( root, rootWorkInProgress, suspendedExpirationTime, rootExpirationTime, -1, // Indicates no timeout ); return; } } if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) { // The tree was suspended. const suspendedExpirationTime = expirationTime; markSuspendedPriorityLevel(root, suspendedExpirationTime); // Find the earliest uncommitted expiration time in the tree, including // work that is suspended. The timeout threshold cannot be longer than // the overall expiration. const earliestExpirationTime = findEarliestOutstandingPriorityLevel( root, expirationTime, ); const earliestExpirationTimeMs = expirationTimeToMs(earliestExpirationTime); if (earliestExpirationTimeMs < nextLatestAbsoluteTimeoutMs) { nextLatestAbsoluteTimeoutMs = earliestExpirationTimeMs; } // Subtract the current time from the absolute timeout to get the number // of milliseconds until the timeout. In other words, convert an absolute // timestamp to a relative time. This is the value that is passed // to `setTimeout`. const currentTimeMs = expirationTimeToMs(requestCurrentTime()); let msUntilTimeout = nextLatestAbsoluteTimeoutMs - currentTimeMs; msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout; // TODO: Account for the Just Noticeable Difference const rootExpirationTime = root.expirationTime; onSuspend( root, rootWorkInProgress, suspendedExpirationTime, rootExpirationTime, msUntilTimeout, ); return; } // Ready to commit. onComplete(root, rootWorkInProgress, expirationTime); }
beginWork 根据 WIP.tag 做处理,先处理子组件,再处理兄弟组件,每次重新赋值 nextUnitOfWork 直到所有单元Unit都执行完毕
应该着重看下这个方法,了解React对不同类型组件做了哪些什么处理。
function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { const updateExpirationTime = workInProgress.expirationTime; if (current !== null) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if (oldProps !== newProps || hasLegacyContextChanged()) { // If props or context changed, mark the fiber as having performed work. // This may be unset if the props are determined to be equal later (memo). didReceiveUpdate = true; } else if (updateExpirationTime < renderExpirationTime) { didReceiveUpdate = false; // This fiber does not have any pending work. Bailout without entering // the begin phase. There's still some bookkeeping we that needs to be done // in this optimized path, mostly pushing stuff onto the stack. switch (workInProgress.tag) { case HostRoot: pushHostRootContext(workInProgress); resetHydrationState(); break; case HostComponent: pushHostContext(workInProgress); break; case ClassComponent: { const Component = workInProgress.type; if (isLegacyContextProvider(Component)) { pushLegacyContextProvider(workInProgress); } break; } case HostPortal: pushHostContainer( workInProgress, workInProgress.stateNode.containerInfo, ); break; case ContextProvider: { const newValue = workInProgress.memoizedProps.value; pushProvider(workInProgress, newValue); break; } case Profiler: if (enableProfilerTimer) { workInProgress.effectTag |= Update; } break; case SuspenseComponent: { const state: SuspenseState | null = workInProgress.memoizedState; const didTimeout = state !== null; if (didTimeout) { // If this boundary is currently timed out, we need to decide // whether to retry the primary children, or to skip over it and // go straight to the fallback. Check the priority of the primary // child fragment. const primaryChildFragment: Fiber = (workInProgress.child: any); const primaryChildExpirationTime = primaryChildFragment.childExpirationTime; if ( primaryChildExpirationTime !== NoWork && primaryChildExpirationTime >= renderExpirationTime ) { // The primary children have pending work. Use the normal path // to attempt to render the primary children again. return updateSuspenseComponent( current, workInProgress, renderExpirationTime, ); } else { // The primary children do not have pending work with sufficient // priority. Bailout. const child = bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); if (child !== null) { // The fallback children have pending work. Skip over the // primary children and work on the fallback. return child.sibling; } else { return null; } } } break; } } return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } } else { didReceiveUpdate = false; } // Before entering the begin phase, clear the expiration time. workInProgress.expirationTime = NoWork; switch (workInProgress.tag) { case IndeterminateComponent: { const elementType = workInProgress.elementType; return mountIndeterminateComponent( current, workInProgress, elementType, renderExpirationTime, ); } case LazyComponent: { const elementType = workInProgress.elementType; return mountLazyComponent( current, workInProgress, elementType, updateExpirationTime, renderExpirationTime, ); } case FunctionComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderExpirationTime, ); } case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateClassComponent( current, workInProgress, Component, resolvedProps, renderExpirationTime, ); } case HostRoot: return updateHostRoot(current, workInProgress, renderExpirationTime); case HostComponent: return updateHostComponent(current, workInProgress, renderExpirationTime); case HostText: return updateHostText(current, workInProgress); case SuspenseComponent: return updateSuspenseComponent( current, workInProgress, renderExpirationTime, ); case HostPortal: return updatePortalComponent( current, workInProgress, renderExpirationTime, ); case ForwardRef: { const type = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === type ? unresolvedProps : resolveDefaultProps(type, unresolvedProps); return updateForwardRef( current, workInProgress, type, resolvedProps, renderExpirationTime, ); } case Fragment: return updateFragment(current, workInProgress, renderExpirationTime); case Mode: return updateMode(current, workInProgress, renderExpirationTime); case Profiler: return updateProfiler(current, workInProgress, renderExpirationTime); case ContextProvider: return updateContextProvider( current, workInProgress, renderExpirationTime, ); case ContextConsumer: return updateContextConsumer( current, workInProgress, renderExpirationTime, ); case MemoComponent: { const type = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props. let resolvedProps = resolveDefaultProps(type, unresolvedProps); if (__DEV__) { if (workInProgress.type !== workInProgress.elementType) { const outerPropTypes = type.propTypes; if (outerPropTypes) { checkPropTypes( outerPropTypes, resolvedProps, // Resolved for outer only 'prop', getComponentName(type), getCurrentFiberStackInDev, ); } } } resolvedProps = resolveDefaultProps(type.type, resolvedProps); return updateMemoComponent( current, workInProgress, type, resolvedProps, updateExpirationTime, renderExpirationTime, ); } case SimpleMemoComponent: { return updateSimpleMemoComponent( current, workInProgress, workInProgress.type, workInProgress.pendingProps, updateExpirationTime, renderExpirationTime, ); } case IncompleteClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return mountIncompleteClassComponent( current, workInProgress, Component, resolvedProps, renderExpirationTime, ); } default: invariant( false, 'Unknown unit of work tag. This error is likely caused by a bug in ' + 'React. Please file an issue.', ); } }