mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Experimental] API for reading context from within any render phase function (#13139)
* Store list of contexts on the fiber Currently, context can only be read by a special type of component, ContextConsumer. We want to add support to all fibers, including classes and functional components. Each fiber may read from one or more contexts. To enable quick, mono- morphic access of this list, we'll store them on a fiber property. * Context.unstable_read unstable_read can be called anywhere within the render phase. That includes the render method, getDerivedStateFromProps, constructors, functional components, and context consumer render props. If it's called outside the render phase, an error is thrown. * Remove vestigial context cursor Wasn't being used. * Split fiber.expirationTime into two separate fields Currently, the `expirationTime` field represents the pending work of both the fiber itself — including new props, state, and context — and of any updates in that fiber's subtree. This commit adds a second field called `childExpirationTime`. Now `expirationTime` only represents the pending work of the fiber itself. The subtree's pending work is represented by `childExpirationTime`. The biggest advantage is it requires fewer checks to bailout on already finished work. For most types of work, if the `expirationTime` does not match the render expiration time, we can bailout immediately without any further checks. This won't work for fibers that have `shouldComponentUpdate` semantics (class components), for which we still need to check for props and state changes explicitly. * Performance nits Optimize `readContext` for most common case
This commit is contained in:
+4
-1
@@ -662,7 +662,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
'- ' +
|
||||
// need to explicitly coerce Symbol to a string
|
||||
(fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'),
|
||||
'[' + fiber.expirationTime + (fiber.pendingProps ? '*' : '') + ']',
|
||||
'[' +
|
||||
fiber.childExpirationTime +
|
||||
(fiber.pendingProps ? '*' : '') +
|
||||
']',
|
||||
);
|
||||
if (fiber.updateQueue) {
|
||||
logUpdateQueue(fiber.updateQueue, depth);
|
||||
|
||||
+22
-2
@@ -14,6 +14,7 @@ import type {TypeOfMode} from './ReactTypeOfMode';
|
||||
import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {UpdateQueue} from './ReactUpdateQueue';
|
||||
import type {ContextDependency} from './ReactFiberNewContext';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
|
||||
@@ -124,6 +125,9 @@ export type Fiber = {|
|
||||
// The state used to create the output
|
||||
memoizedState: any,
|
||||
|
||||
// A linked-list of contexts that this fiber depends on
|
||||
firstContextDependency: ContextDependency<mixed> | null,
|
||||
|
||||
// Bitfield that describes properties about the fiber and its subtree. E.g.
|
||||
// the AsyncMode flag indicates whether the subtree should be async-by-
|
||||
// default. When a fiber is created, it inherits the mode of its
|
||||
@@ -145,9 +149,12 @@ export type Fiber = {|
|
||||
lastEffect: Fiber | null,
|
||||
|
||||
// Represents a time in the future by which this work should be completed.
|
||||
// This is also used to quickly determine if a subtree has no pending changes.
|
||||
// Does not include work found in its subtree.
|
||||
expirationTime: ExpirationTime,
|
||||
|
||||
// This is used to quickly determine if a subtree has no pending changes.
|
||||
childExpirationTime: ExpirationTime,
|
||||
|
||||
// This is a pooled version of a Fiber. Every fiber that gets updated will
|
||||
// eventually have a pair. There are cases when we can clean up pairs to save
|
||||
// memory if we need to.
|
||||
@@ -213,6 +220,7 @@ function FiberNode(
|
||||
this.memoizedProps = null;
|
||||
this.updateQueue = null;
|
||||
this.memoizedState = null;
|
||||
this.firstContextDependency = null;
|
||||
|
||||
this.mode = mode;
|
||||
|
||||
@@ -224,6 +232,7 @@ function FiberNode(
|
||||
this.lastEffect = null;
|
||||
|
||||
this.expirationTime = NoWork;
|
||||
this.childExpirationTime = NoWork;
|
||||
|
||||
this.alternate = null;
|
||||
|
||||
@@ -325,12 +334,21 @@ export function createWorkInProgress(
|
||||
}
|
||||
}
|
||||
|
||||
workInProgress.expirationTime = expirationTime;
|
||||
// Don't touching the subtree's expiration time, which has not changed.
|
||||
workInProgress.childExpirationTime = current.childExpirationTime;
|
||||
if (pendingProps !== current.pendingProps) {
|
||||
// This fiber has new props.
|
||||
workInProgress.expirationTime = expirationTime;
|
||||
} else {
|
||||
// This fiber's props have not changed.
|
||||
workInProgress.expirationTime = current.expirationTime;
|
||||
}
|
||||
|
||||
workInProgress.child = current.child;
|
||||
workInProgress.memoizedProps = current.memoizedProps;
|
||||
workInProgress.memoizedState = current.memoizedState;
|
||||
workInProgress.updateQueue = current.updateQueue;
|
||||
workInProgress.firstContextDependency = current.firstContextDependency;
|
||||
|
||||
// These will be overridden during the parent's reconciliation
|
||||
workInProgress.sibling = current.sibling;
|
||||
@@ -559,12 +577,14 @@ export function assignFiberPropertiesInDEV(
|
||||
target.memoizedProps = source.memoizedProps;
|
||||
target.updateQueue = source.updateQueue;
|
||||
target.memoizedState = source.memoizedState;
|
||||
target.firstContextDependency = source.firstContextDependency;
|
||||
target.mode = source.mode;
|
||||
target.effectTag = source.effectTag;
|
||||
target.nextEffect = source.nextEffect;
|
||||
target.firstEffect = source.firstEffect;
|
||||
target.lastEffect = source.lastEffect;
|
||||
target.expirationTime = source.expirationTime;
|
||||
target.childExpirationTime = source.childExpirationTime;
|
||||
target.alternate = source.alternate;
|
||||
if (enableProfilerTimer) {
|
||||
target.actualDuration = source.actualDuration;
|
||||
|
||||
+257
-374
@@ -70,8 +70,9 @@ import {
|
||||
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext';
|
||||
import {
|
||||
pushProvider,
|
||||
getContextCurrentValue,
|
||||
getContextChangedBits,
|
||||
propagateContextChange,
|
||||
readContext,
|
||||
prepareToReadContext,
|
||||
} from './ReactFiberNewContext';
|
||||
import {
|
||||
markActualRenderTimeStarted,
|
||||
@@ -111,17 +112,7 @@ if (__DEV__) {
|
||||
didWarnAboutStatelessRefs = {};
|
||||
}
|
||||
|
||||
// TODO: Remove this and use reconcileChildrenAtExpirationTime directly.
|
||||
function reconcileChildren(current, workInProgress, nextChildren) {
|
||||
reconcileChildrenAtExpirationTime(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
workInProgress.expirationTime,
|
||||
);
|
||||
}
|
||||
|
||||
export function reconcileChildrenAtExpirationTime(
|
||||
export function reconcileChildren(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
nextChildren: any,
|
||||
@@ -154,7 +145,7 @@ export function reconcileChildrenAtExpirationTime(
|
||||
}
|
||||
}
|
||||
|
||||
function updateForwardRef(current, workInProgress) {
|
||||
function updateForwardRef(current, workInProgress, renderExpirationTime) {
|
||||
const render = workInProgress.type.render;
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const ref = workInProgress.ref;
|
||||
@@ -164,7 +155,11 @@ function updateForwardRef(current, workInProgress) {
|
||||
} else if (workInProgress.memoizedProps === nextProps) {
|
||||
const currentRef = current !== null ? current.ref : null;
|
||||
if (ref === currentRef) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,50 +173,52 @@ function updateForwardRef(current, workInProgress) {
|
||||
nextChildren = render(nextProps, ref);
|
||||
}
|
||||
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateFragment(current, workInProgress) {
|
||||
function updateFragment(current, workInProgress, renderExpirationTime) {
|
||||
const nextChildren = workInProgress.pendingProps;
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (workInProgress.memoizedProps === nextChildren) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
memoizeProps(workInProgress, nextChildren);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateMode(current, workInProgress) {
|
||||
function updateMode(current, workInProgress, renderExpirationTime) {
|
||||
const nextChildren = workInProgress.pendingProps.children;
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (
|
||||
nextChildren === null ||
|
||||
workInProgress.memoizedProps === nextChildren
|
||||
) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
memoizeProps(workInProgress, nextChildren);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateProfiler(current, workInProgress) {
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
function updateProfiler(current, workInProgress, renderExpirationTime) {
|
||||
if (enableProfilerTimer) {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
if (workInProgress.memoizedProps === nextProps) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const nextChildren = nextProps.children;
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
return workInProgress.child;
|
||||
}
|
||||
@@ -237,26 +234,18 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateFunctionalComponent(current, workInProgress) {
|
||||
function updateFunctionalComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
) {
|
||||
const fn = workInProgress.type;
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else {
|
||||
if (workInProgress.memoizedProps === nextProps) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
// TODO: consider bringing fn.shouldComponentUpdate() back.
|
||||
// It used to be here.
|
||||
}
|
||||
|
||||
const unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
let nextChildren;
|
||||
|
||||
prepareToReadContext(workInProgress, renderExpirationTime);
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
ReactCurrentFiber.setCurrentPhase('render');
|
||||
@@ -265,9 +254,15 @@ function updateFunctionalComponent(current, workInProgress) {
|
||||
} else {
|
||||
nextChildren = fn(nextProps, context);
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.effectTag |= PerformedWork;
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
return workInProgress.child;
|
||||
}
|
||||
@@ -281,6 +276,11 @@ function updateClassComponent(
|
||||
// During mounting we don't know the child context yet as the instance doesn't exist.
|
||||
// We will invalidate the child context in finishClassComponent() right after rendering.
|
||||
const hasContext = pushLegacyContextProvider(workInProgress);
|
||||
const hasPendingNewContext = prepareToReadContext(
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
|
||||
let shouldUpdate;
|
||||
if (current === null) {
|
||||
if (workInProgress.stateNode === null) {
|
||||
@@ -297,6 +297,7 @@ function updateClassComponent(
|
||||
// In a resume, we'll already have an instance we can reuse.
|
||||
shouldUpdate = resumeMountClassInstance(
|
||||
workInProgress,
|
||||
hasPendingNewContext,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
@@ -304,6 +305,7 @@ function updateClassComponent(
|
||||
shouldUpdate = updateClassInstance(
|
||||
current,
|
||||
workInProgress,
|
||||
hasPendingNewContext,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
@@ -334,7 +336,11 @@ function finishClassComponent(
|
||||
invalidateContextProvider(workInProgress, false);
|
||||
}
|
||||
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
|
||||
const ctor = workInProgress.type;
|
||||
@@ -380,18 +386,13 @@ function finishClassComponent(
|
||||
if (current !== null && didCaptureError) {
|
||||
// If we're recovering from an error, reconcile twice: first to delete
|
||||
// all the existing children.
|
||||
reconcileChildrenAtExpirationTime(
|
||||
current,
|
||||
workInProgress,
|
||||
null,
|
||||
renderExpirationTime,
|
||||
);
|
||||
reconcileChildren(current, workInProgress, null, renderExpirationTime);
|
||||
workInProgress.child = null;
|
||||
// Now we can continue reconciling like normal. This has the effect of
|
||||
// remounting all children regardless of whether their their
|
||||
// identity matches.
|
||||
}
|
||||
reconcileChildrenAtExpirationTime(
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
@@ -427,66 +428,75 @@ function pushHostRootContext(workInProgress) {
|
||||
|
||||
function updateHostRoot(current, workInProgress, renderExpirationTime) {
|
||||
pushHostRootContext(workInProgress);
|
||||
let updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const prevState = workInProgress.memoizedState;
|
||||
const prevChildren = prevState !== null ? prevState.element : null;
|
||||
processUpdateQueue(
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
invariant(
|
||||
updateQueue !== null,
|
||||
'If the root does not have an updateQueue, we should have already ' +
|
||||
'bailed out. This error is likely caused by a bug in React. Please ' +
|
||||
'file an issue.',
|
||||
);
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const prevState = workInProgress.memoizedState;
|
||||
const prevChildren = prevState !== null ? prevState.element : null;
|
||||
processUpdateQueue(
|
||||
workInProgress,
|
||||
updateQueue,
|
||||
nextProps,
|
||||
null,
|
||||
renderExpirationTime,
|
||||
);
|
||||
const nextState = workInProgress.memoizedState;
|
||||
// Caution: React DevTools currently depends on this property
|
||||
// being called "element".
|
||||
const nextChildren = nextState.element;
|
||||
if (nextChildren === prevChildren) {
|
||||
// If the state is the same as before, that's a bailout because we had
|
||||
// no work that expires at this time.
|
||||
resetHydrationState();
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
updateQueue,
|
||||
nextProps,
|
||||
null,
|
||||
renderExpirationTime,
|
||||
);
|
||||
const nextState = workInProgress.memoizedState;
|
||||
// Caution: React DevTools currently depends on this property
|
||||
// being called "element".
|
||||
const nextChildren = nextState.element;
|
||||
|
||||
if (nextChildren === prevChildren) {
|
||||
// If the state is the same as before, that's a bailout because we had
|
||||
// no work that expires at this time.
|
||||
resetHydrationState();
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
const root: FiberRoot = workInProgress.stateNode;
|
||||
if (
|
||||
(current === null || current.child === null) &&
|
||||
root.hydrate &&
|
||||
enterHydrationState(workInProgress)
|
||||
) {
|
||||
// If we don't have any current children this might be the first pass.
|
||||
// We always try to hydrate. If this isn't a hydration pass there won't
|
||||
// be any children to hydrate which is effectively the same thing as
|
||||
// not hydrating.
|
||||
|
||||
// This is a bit of a hack. We track the host root as a placement to
|
||||
// know that we're currently in a mounting state. That way isMounted
|
||||
// works as expected. We must reset this before committing.
|
||||
// TODO: Delete this when we delete isMounted and findDOMNode.
|
||||
workInProgress.effectTag |= Placement;
|
||||
|
||||
// Ensure that children mount into this root without tracking
|
||||
// side-effects. This ensures that we don't store Placement effects on
|
||||
// nodes that will be hydrated.
|
||||
workInProgress.child = mountChildFibers(
|
||||
workInProgress,
|
||||
null,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
} else {
|
||||
// Otherwise reset hydration state in case we aborted and resumed another
|
||||
// root.
|
||||
resetHydrationState();
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
resetHydrationState();
|
||||
// If there is no update queue, that's a bailout because the root has no props.
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
const root: FiberRoot = workInProgress.stateNode;
|
||||
if (
|
||||
(current === null || current.child === null) &&
|
||||
root.hydrate &&
|
||||
enterHydrationState(workInProgress)
|
||||
) {
|
||||
// If we don't have any current children this might be the first pass.
|
||||
// We always try to hydrate. If this isn't a hydration pass there won't
|
||||
// be any children to hydrate which is effectively the same thing as
|
||||
// not hydrating.
|
||||
|
||||
// This is a bit of a hack. We track the host root as a placement to
|
||||
// know that we're currently in a mounting state. That way isMounted
|
||||
// works as expected. We must reset this before committing.
|
||||
// TODO: Delete this when we delete isMounted and findDOMNode.
|
||||
workInProgress.effectTag |= Placement;
|
||||
|
||||
// Ensure that children mount into this root without tracking
|
||||
// side-effects. This ensures that we don't store Placement effects on
|
||||
// nodes that will be hydrated.
|
||||
workInProgress.child = mountChildFibers(
|
||||
workInProgress,
|
||||
null,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
} else {
|
||||
// Otherwise reset hydration state in case we aborted and resumed another
|
||||
// root.
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
resetHydrationState();
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateHostComponent(current, workInProgress, renderExpirationTime) {
|
||||
@@ -497,28 +507,9 @@ function updateHostComponent(current, workInProgress, renderExpirationTime) {
|
||||
}
|
||||
|
||||
const type = workInProgress.type;
|
||||
const memoizedProps = workInProgress.memoizedProps;
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const prevProps = current !== null ? current.memoizedProps : null;
|
||||
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (memoizedProps === nextProps) {
|
||||
const isHidden =
|
||||
workInProgress.mode & AsyncMode &&
|
||||
shouldDeprioritizeSubtree(type, nextProps);
|
||||
if (isHidden) {
|
||||
// Before bailing out, make sure we've deprioritized a hidden component.
|
||||
workInProgress.expirationTime = Never;
|
||||
}
|
||||
if (!isHidden || renderExpirationTime !== Never) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
// If we're rendering a hidden node at hidden priority, don't bailout. The
|
||||
// parent is complete, but the children may not be.
|
||||
}
|
||||
|
||||
let nextChildren = nextProps.children;
|
||||
const isDirectTextChild = shouldSetTextContent(type, nextProps);
|
||||
|
||||
@@ -528,7 +519,7 @@ function updateHostComponent(current, workInProgress, renderExpirationTime) {
|
||||
// this in the host environment that also have access to this prop. That
|
||||
// avoids allocating another HostText fiber and traversing it.
|
||||
nextChildren = null;
|
||||
} else if (prevProps && shouldSetTextContent(type, prevProps)) {
|
||||
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
|
||||
// If we're switching from a direct text child to a normal child, or to
|
||||
// empty, we need to schedule the text content to be reset.
|
||||
workInProgress.effectTag |= ContentReset;
|
||||
@@ -542,14 +533,18 @@ function updateHostComponent(current, workInProgress, renderExpirationTime) {
|
||||
workInProgress.mode & AsyncMode &&
|
||||
shouldDeprioritizeSubtree(type, nextProps)
|
||||
) {
|
||||
// Down-prioritize the children.
|
||||
// Schedule this fiber to re-render at offscreen priority. Then bailout.
|
||||
workInProgress.expirationTime = Never;
|
||||
// Bailout and come back to this fiber later.
|
||||
workInProgress.memoizedProps = nextProps;
|
||||
return null;
|
||||
}
|
||||
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
return workInProgress.child;
|
||||
}
|
||||
@@ -580,6 +575,8 @@ function mountIndeterminateComponent(
|
||||
const unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
prepareToReadContext(workInProgress, renderExpirationTime);
|
||||
|
||||
let value;
|
||||
|
||||
if (__DEV__) {
|
||||
@@ -697,7 +694,7 @@ function mountIndeterminateComponent(
|
||||
}
|
||||
}
|
||||
}
|
||||
reconcileChildren(current, workInProgress, value);
|
||||
reconcileChildren(current, workInProgress, value, renderExpirationTime);
|
||||
memoizeProps(workInProgress, props);
|
||||
return workInProgress.child;
|
||||
}
|
||||
@@ -710,9 +707,6 @@ function updatePlaceholderComponent(
|
||||
) {
|
||||
if (enableSuspense) {
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const prevProps = workInProgress.memoizedProps;
|
||||
|
||||
const prevDidTimeout = workInProgress.memoizedState === true;
|
||||
|
||||
// Check if we already attempted to render the normal state. If we did,
|
||||
// and we timed out, render the placeholder state.
|
||||
@@ -727,12 +721,7 @@ function updatePlaceholderComponent(
|
||||
nextDidTimeout = true;
|
||||
// If we're recovering from an error, reconcile twice: first to delete
|
||||
// all the existing children.
|
||||
reconcileChildrenAtExpirationTime(
|
||||
current,
|
||||
workInProgress,
|
||||
null,
|
||||
renderExpirationTime,
|
||||
);
|
||||
reconcileChildren(current, workInProgress, null, renderExpirationTime);
|
||||
current.child = null;
|
||||
// Now we can continue reconciling like normal. This has the effect of
|
||||
// remounting all children regardless of whether their their
|
||||
@@ -741,13 +730,6 @@ function updatePlaceholderComponent(
|
||||
nextDidTimeout = !alreadyCaptured;
|
||||
}
|
||||
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (nextProps === prevProps && nextDidTimeout === prevDidTimeout) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
if ((workInProgress.mode & StrictMode) !== NoEffect) {
|
||||
if (nextDidTimeout) {
|
||||
// If the timed-out view commits, schedule an update effect to record
|
||||
@@ -772,7 +754,12 @@ function updatePlaceholderComponent(
|
||||
|
||||
workInProgress.memoizedProps = nextProps;
|
||||
workInProgress.memoizedState = nextDidTimeout;
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
return workInProgress.child;
|
||||
} else {
|
||||
return null;
|
||||
@@ -782,13 +769,6 @@ function updatePlaceholderComponent(
|
||||
function updatePortalComponent(current, workInProgress, renderExpirationTime) {
|
||||
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
|
||||
const nextChildren = workInProgress.pendingProps;
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (workInProgress.memoizedProps === nextChildren) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
if (current === null) {
|
||||
// Portals are special because we don't append the children during mount
|
||||
// but at commit. Therefore we need to track insertions which the normal
|
||||
@@ -803,125 +783,23 @@ function updatePortalComponent(current, workInProgress, renderExpirationTime) {
|
||||
);
|
||||
memoizeProps(workInProgress, nextChildren);
|
||||
} else {
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
memoizeProps(workInProgress, nextChildren);
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function propagateContextChange<V>(
|
||||
workInProgress: Fiber,
|
||||
context: ReactContext<V>,
|
||||
changedBits: number,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): void {
|
||||
let fiber = workInProgress.child;
|
||||
if (fiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
fiber.return = workInProgress;
|
||||
}
|
||||
while (fiber !== null) {
|
||||
let nextFiber;
|
||||
// Visit this fiber.
|
||||
switch (fiber.tag) {
|
||||
case ContextConsumer:
|
||||
// Check if the context matches.
|
||||
const observedBits: number = fiber.stateNode | 0;
|
||||
if (fiber.type === context && (observedBits & changedBits) !== 0) {
|
||||
// Update the expiration time of all the ancestors, including
|
||||
// the alternates.
|
||||
|
||||
let node = fiber;
|
||||
do {
|
||||
const alternate = node.alternate;
|
||||
if (
|
||||
node.expirationTime === NoWork ||
|
||||
node.expirationTime > renderExpirationTime
|
||||
) {
|
||||
node.expirationTime = renderExpirationTime;
|
||||
if (
|
||||
alternate !== null &&
|
||||
(alternate.expirationTime === NoWork ||
|
||||
alternate.expirationTime > renderExpirationTime)
|
||||
) {
|
||||
alternate.expirationTime = renderExpirationTime;
|
||||
}
|
||||
} else if (
|
||||
alternate !== null &&
|
||||
(alternate.expirationTime === NoWork ||
|
||||
alternate.expirationTime > renderExpirationTime)
|
||||
) {
|
||||
alternate.expirationTime = renderExpirationTime;
|
||||
} else {
|
||||
// Neither alternate was updated, which means the rest of the
|
||||
// ancestor path already has sufficient priority.
|
||||
break;
|
||||
}
|
||||
node = node.return;
|
||||
} while (node !== null);
|
||||
|
||||
// Don't scan deeper than a matching consumer. When we render the
|
||||
// consumer, we'll continue scanning from that point. This way the
|
||||
// scanning work is time-sliced.
|
||||
nextFiber = null;
|
||||
} else {
|
||||
// Traverse down.
|
||||
nextFiber = fiber.child;
|
||||
}
|
||||
break;
|
||||
case ContextProvider:
|
||||
// Don't scan deeper if this is a matching provider
|
||||
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
|
||||
break;
|
||||
default:
|
||||
// Traverse down.
|
||||
nextFiber = fiber.child;
|
||||
break;
|
||||
}
|
||||
if (nextFiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
nextFiber.return = fiber;
|
||||
} else {
|
||||
// No child. Traverse to next sibling.
|
||||
nextFiber = fiber;
|
||||
while (nextFiber !== null) {
|
||||
if (nextFiber === workInProgress) {
|
||||
// We're back to the root of this subtree. Exit.
|
||||
nextFiber = null;
|
||||
break;
|
||||
}
|
||||
let sibling = nextFiber.sibling;
|
||||
if (sibling !== null) {
|
||||
// Set the return pointer of the sibling to the work-in-progress fiber.
|
||||
sibling.return = nextFiber.return;
|
||||
nextFiber = sibling;
|
||||
break;
|
||||
}
|
||||
// No more siblings. Traverse up.
|
||||
nextFiber = nextFiber.return;
|
||||
}
|
||||
}
|
||||
fiber = nextFiber;
|
||||
}
|
||||
}
|
||||
|
||||
function updateContextProvider(current, workInProgress, renderExpirationTime) {
|
||||
const providerType: ReactProviderType<any> = workInProgress.type;
|
||||
const context: ReactContext<any> = providerType._context;
|
||||
|
||||
const newProps = workInProgress.pendingProps;
|
||||
const oldProps = workInProgress.memoizedProps;
|
||||
let canBailOnProps = true;
|
||||
|
||||
if (hasLegacyContextChanged()) {
|
||||
canBailOnProps = false;
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (oldProps === newProps) {
|
||||
workInProgress.stateNode = 0;
|
||||
pushProvider(workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
const newValue = newProps.value;
|
||||
workInProgress.memoizedProps = newProps;
|
||||
@@ -947,10 +825,17 @@ function updateContextProvider(current, workInProgress, renderExpirationTime) {
|
||||
} else {
|
||||
if (oldProps.value === newProps.value) {
|
||||
// No change. Bailout early if children are the same.
|
||||
if (oldProps.children === newProps.children && canBailOnProps) {
|
||||
if (
|
||||
oldProps.children === newProps.children &&
|
||||
!hasLegacyContextChanged()
|
||||
) {
|
||||
workInProgress.stateNode = 0;
|
||||
pushProvider(workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
changedBits = 0;
|
||||
} else {
|
||||
@@ -964,10 +849,17 @@ function updateContextProvider(current, workInProgress, renderExpirationTime) {
|
||||
(oldValue !== oldValue && newValue !== newValue) // eslint-disable-line no-self-compare
|
||||
) {
|
||||
// No change. Bailout early if children are the same.
|
||||
if (oldProps.children === newProps.children && canBailOnProps) {
|
||||
if (
|
||||
oldProps.children === newProps.children &&
|
||||
!hasLegacyContextChanged()
|
||||
) {
|
||||
workInProgress.stateNode = 0;
|
||||
pushProvider(workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
changedBits = 0;
|
||||
} else {
|
||||
@@ -987,10 +879,17 @@ function updateContextProvider(current, workInProgress, renderExpirationTime) {
|
||||
|
||||
if (changedBits === 0) {
|
||||
// No change. Bailout early if children are the same.
|
||||
if (oldProps.children === newProps.children && canBailOnProps) {
|
||||
if (
|
||||
oldProps.children === newProps.children &&
|
||||
!hasLegacyContextChanged()
|
||||
) {
|
||||
workInProgress.stateNode = 0;
|
||||
pushProvider(workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
propagateContextChange(
|
||||
@@ -1008,52 +907,13 @@ function updateContextProvider(current, workInProgress, renderExpirationTime) {
|
||||
pushProvider(workInProgress);
|
||||
|
||||
const newChildren = newProps.children;
|
||||
reconcileChildren(current, workInProgress, newChildren);
|
||||
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateContextConsumer(current, workInProgress, renderExpirationTime) {
|
||||
const context: ReactContext<any> = workInProgress.type;
|
||||
const newProps = workInProgress.pendingProps;
|
||||
const oldProps = workInProgress.memoizedProps;
|
||||
|
||||
const newValue = getContextCurrentValue(context);
|
||||
const changedBits = getContextChangedBits(context);
|
||||
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (changedBits === 0 && oldProps === newProps) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
workInProgress.memoizedProps = newProps;
|
||||
|
||||
let observedBits = newProps.unstable_observedBits;
|
||||
if (observedBits === undefined || observedBits === null) {
|
||||
// Subscribe to all changes by default
|
||||
observedBits = MAX_SIGNED_31_BIT_INT;
|
||||
}
|
||||
// Store the observedBits on the fiber's stateNode for quick access.
|
||||
workInProgress.stateNode = observedBits;
|
||||
|
||||
if ((changedBits & observedBits) !== 0) {
|
||||
// Context change propagation stops at matching consumers, for time-
|
||||
// slicing. Continue the propagation here.
|
||||
propagateContextChange(
|
||||
workInProgress,
|
||||
context,
|
||||
changedBits,
|
||||
renderExpirationTime,
|
||||
);
|
||||
} else if (oldProps === newProps) {
|
||||
// Skip over a memoized parent with a bitmask bailout even
|
||||
// if we began working on it because of a deeper matching child.
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
// There is no bailout on `children` equality because we expect people
|
||||
// to often pass a bound method as a child, but it may reference
|
||||
// `this.state` or `this.props` (and thus needs to re-render on `setState`).
|
||||
|
||||
const render = newProps.children;
|
||||
|
||||
if (__DEV__) {
|
||||
@@ -1066,6 +926,8 @@ function updateContextConsumer(current, workInProgress, renderExpirationTime) {
|
||||
);
|
||||
}
|
||||
|
||||
prepareToReadContext(workInProgress, renderExpirationTime);
|
||||
const newValue = readContext(context, newProps.unstable_observedBits);
|
||||
let newChildren;
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
@@ -1078,7 +940,8 @@ function updateContextConsumer(current, workInProgress, renderExpirationTime) {
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.effectTag |= PerformedWork;
|
||||
reconcileChildren(current, workInProgress, newChildren);
|
||||
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
|
||||
workInProgress.memoizedProps = newProps;
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
@@ -1102,61 +965,38 @@ function updateContextConsumer(current, workInProgress, renderExpirationTime) {
|
||||
*/
|
||||
|
||||
function bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): Fiber | null {
|
||||
cancelWorkTimer(workInProgress);
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Don't update "base" render times for bailouts.
|
||||
stopBaseRenderTimerIfRunning();
|
||||
if (current !== null) {
|
||||
// Reuse previous context list
|
||||
workInProgress.firstContextDependency = current.firstContextDependency;
|
||||
}
|
||||
|
||||
// TODO: We should ideally be able to bail out early if the children have no
|
||||
// more work to do. However, since we don't have a separation of this
|
||||
// Fiber's priority and its children yet - we don't know without doing lots
|
||||
// of the same work we do anyway. Once we have that separation we can just
|
||||
// bail out here if the children has no more work at this priority level.
|
||||
// if (workInProgress.priorityOfChildren <= priorityLevel) {
|
||||
// // If there are side-effects in these children that have not yet been
|
||||
// // committed we need to ensure that they get properly transferred up.
|
||||
// if (current && current.child !== workInProgress.child) {
|
||||
// reuseChildrenEffects(workInProgress, child);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
cloneChildFibers(current, workInProgress);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function bailoutOnLowPriority(current, workInProgress) {
|
||||
cancelWorkTimer(workInProgress);
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Don't update "base" render times for bailouts.
|
||||
stopBaseRenderTimerIfRunning();
|
||||
}
|
||||
|
||||
// TODO: Handle HostComponent tags here as well and call pushHostContext()?
|
||||
// See PR 8590 discussion for context
|
||||
switch (workInProgress.tag) {
|
||||
case HostRoot:
|
||||
pushHostRootContext(workInProgress);
|
||||
break;
|
||||
case ClassComponent:
|
||||
pushLegacyContextProvider(workInProgress);
|
||||
break;
|
||||
case HostPortal:
|
||||
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
|
||||
break;
|
||||
case ContextProvider:
|
||||
pushProvider(workInProgress);
|
||||
break;
|
||||
// Check if the children have any pending work.
|
||||
const childExpirationTime = workInProgress.childExpirationTime;
|
||||
if (
|
||||
childExpirationTime === NoWork ||
|
||||
childExpirationTime > renderExpirationTime
|
||||
) {
|
||||
// The children don't have any work either. We can skip them.
|
||||
// TODO: Once we add back resuming, we should check if the children are
|
||||
// a work-in-progress set. If so, we need to transfer their effects.
|
||||
return null;
|
||||
} else {
|
||||
// This fiber doesn't have work, but its subtree does. Clone the child
|
||||
// fibers and continue.
|
||||
cloneChildFibers(current, workInProgress);
|
||||
return workInProgress.child;
|
||||
}
|
||||
// TODO: What if this is currently in progress?
|
||||
// How can that happen? How is this not being cloned?
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Delete memoizeProps/State and move to reconcile/bailout instead
|
||||
@@ -1181,13 +1021,52 @@ function beginWork(
|
||||
}
|
||||
}
|
||||
|
||||
const updateExpirationTime = workInProgress.expirationTime;
|
||||
if (
|
||||
workInProgress.expirationTime === NoWork ||
|
||||
workInProgress.expirationTime > renderExpirationTime
|
||||
!hasLegacyContextChanged() &&
|
||||
(updateExpirationTime === NoWork ||
|
||||
updateExpirationTime > renderExpirationTime)
|
||||
) {
|
||||
return bailoutOnLowPriority(current, workInProgress);
|
||||
// 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:
|
||||
pushLegacyContextProvider(workInProgress);
|
||||
break;
|
||||
case HostPortal:
|
||||
pushHostContainer(
|
||||
workInProgress,
|
||||
workInProgress.stateNode.containerInfo,
|
||||
);
|
||||
break;
|
||||
case ContextProvider:
|
||||
workInProgress.stateNode = 0;
|
||||
pushProvider(workInProgress);
|
||||
break;
|
||||
case Profiler:
|
||||
if (enableProfilerTimer) {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
|
||||
// Before entering the begin phase, clear the expiration time.
|
||||
workInProgress.expirationTime = NoWork;
|
||||
|
||||
switch (workInProgress.tag) {
|
||||
case IndeterminateComponent:
|
||||
return mountIndeterminateComponent(
|
||||
@@ -1196,7 +1075,11 @@ function beginWork(
|
||||
renderExpirationTime,
|
||||
);
|
||||
case FunctionalComponent:
|
||||
return updateFunctionalComponent(current, workInProgress);
|
||||
return updateFunctionalComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
case ClassComponent:
|
||||
return updateClassComponent(
|
||||
current,
|
||||
@@ -1222,13 +1105,13 @@ function beginWork(
|
||||
renderExpirationTime,
|
||||
);
|
||||
case ForwardRef:
|
||||
return updateForwardRef(current, workInProgress);
|
||||
return updateForwardRef(current, workInProgress, renderExpirationTime);
|
||||
case Fragment:
|
||||
return updateFragment(current, workInProgress);
|
||||
return updateFragment(current, workInProgress, renderExpirationTime);
|
||||
case Mode:
|
||||
return updateMode(current, workInProgress);
|
||||
return updateMode(current, workInProgress, renderExpirationTime);
|
||||
case Profiler:
|
||||
return updateProfiler(current, workInProgress);
|
||||
return updateProfiler(current, workInProgress, renderExpirationTime);
|
||||
case ContextProvider:
|
||||
return updateContextProvider(
|
||||
current,
|
||||
|
||||
+39
-23
@@ -162,7 +162,7 @@ export function applyDerivedStateFromProps(
|
||||
// Once the update queue is empty, persist the derived state onto the
|
||||
// base state.
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null && updateQueue.expirationTime === NoWork) {
|
||||
if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
|
||||
updateQueue.baseState = memoizedState;
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ const classComponentUpdater = {
|
||||
update.callback = callback;
|
||||
}
|
||||
|
||||
enqueueUpdate(fiber, update, expirationTime);
|
||||
enqueueUpdate(fiber, update);
|
||||
scheduleWork(fiber, expirationTime);
|
||||
},
|
||||
enqueueReplaceState(inst, payload, callback) {
|
||||
@@ -202,7 +202,7 @@ const classComponentUpdater = {
|
||||
update.callback = callback;
|
||||
}
|
||||
|
||||
enqueueUpdate(fiber, update, expirationTime);
|
||||
enqueueUpdate(fiber, update);
|
||||
scheduleWork(fiber, expirationTime);
|
||||
},
|
||||
enqueueForceUpdate(inst, callback) {
|
||||
@@ -220,7 +220,7 @@ const classComponentUpdater = {
|
||||
update.callback = callback;
|
||||
}
|
||||
|
||||
enqueueUpdate(fiber, update, expirationTime);
|
||||
enqueueUpdate(fiber, update);
|
||||
scheduleWork(fiber, expirationTime);
|
||||
},
|
||||
};
|
||||
@@ -231,7 +231,7 @@ function checkShouldComponentUpdate(
|
||||
newProps,
|
||||
oldState,
|
||||
newState,
|
||||
newContext,
|
||||
nextLegacyContext,
|
||||
) {
|
||||
const instance = workInProgress.stateNode;
|
||||
const ctor = workInProgress.type;
|
||||
@@ -240,7 +240,7 @@ function checkShouldComponentUpdate(
|
||||
const shouldUpdate = instance.shouldComponentUpdate(
|
||||
newProps,
|
||||
newState,
|
||||
newContext,
|
||||
nextLegacyContext,
|
||||
);
|
||||
stopPhaseTimer();
|
||||
|
||||
@@ -620,15 +620,15 @@ function callComponentWillReceiveProps(
|
||||
workInProgress,
|
||||
instance,
|
||||
newProps,
|
||||
newContext,
|
||||
nextLegacyContext,
|
||||
) {
|
||||
const oldState = instance.state;
|
||||
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
|
||||
if (typeof instance.componentWillReceiveProps === 'function') {
|
||||
instance.componentWillReceiveProps(newProps, newContext);
|
||||
instance.componentWillReceiveProps(newProps, nextLegacyContext);
|
||||
}
|
||||
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
|
||||
instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
|
||||
instance.UNSAFE_componentWillReceiveProps(newProps, nextLegacyContext);
|
||||
}
|
||||
stopPhaseTimer();
|
||||
|
||||
@@ -741,6 +741,7 @@ function mountClassInstance(
|
||||
|
||||
function resumeMountClassInstance(
|
||||
workInProgress: Fiber,
|
||||
hasPendingNewContext: boolean,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): boolean {
|
||||
const ctor = workInProgress.type;
|
||||
@@ -751,8 +752,11 @@ function resumeMountClassInstance(
|
||||
instance.props = oldProps;
|
||||
|
||||
const oldContext = instance.context;
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const nextLegacyContext = getMaskedContext(
|
||||
workInProgress,
|
||||
nextLegacyUnmaskedContext,
|
||||
);
|
||||
|
||||
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
|
||||
const hasNewLifecycles =
|
||||
@@ -770,12 +774,12 @@ function resumeMountClassInstance(
|
||||
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
|
||||
typeof instance.componentWillReceiveProps === 'function')
|
||||
) {
|
||||
if (oldProps !== newProps || oldContext !== newContext) {
|
||||
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
|
||||
callComponentWillReceiveProps(
|
||||
workInProgress,
|
||||
instance,
|
||||
newProps,
|
||||
newContext,
|
||||
nextLegacyContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -799,6 +803,7 @@ function resumeMountClassInstance(
|
||||
oldProps === newProps &&
|
||||
oldState === newState &&
|
||||
!hasContextChanged() &&
|
||||
!hasPendingNewContext &&
|
||||
!checkHasForceUpdateAfterProcessing()
|
||||
) {
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
@@ -820,13 +825,14 @@ function resumeMountClassInstance(
|
||||
|
||||
const shouldUpdate =
|
||||
checkHasForceUpdateAfterProcessing() ||
|
||||
hasPendingNewContext ||
|
||||
checkShouldComponentUpdate(
|
||||
workInProgress,
|
||||
oldProps,
|
||||
newProps,
|
||||
oldState,
|
||||
newState,
|
||||
newContext,
|
||||
nextLegacyContext,
|
||||
);
|
||||
|
||||
if (shouldUpdate) {
|
||||
@@ -866,7 +872,7 @@ function resumeMountClassInstance(
|
||||
// if shouldComponentUpdate returns false.
|
||||
instance.props = newProps;
|
||||
instance.state = newState;
|
||||
instance.context = newContext;
|
||||
instance.context = nextLegacyContext;
|
||||
|
||||
return shouldUpdate;
|
||||
}
|
||||
@@ -875,6 +881,7 @@ function resumeMountClassInstance(
|
||||
function updateClassInstance(
|
||||
current: Fiber,
|
||||
workInProgress: Fiber,
|
||||
hasPendingNewContext: boolean,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): boolean {
|
||||
const ctor = workInProgress.type;
|
||||
@@ -885,8 +892,11 @@ function updateClassInstance(
|
||||
instance.props = oldProps;
|
||||
|
||||
const oldContext = instance.context;
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const nextLegacyContext = getMaskedContext(
|
||||
workInProgress,
|
||||
nextLegacyUnmaskedContext,
|
||||
);
|
||||
|
||||
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
|
||||
const hasNewLifecycles =
|
||||
@@ -904,12 +914,12 @@ function updateClassInstance(
|
||||
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
|
||||
typeof instance.componentWillReceiveProps === 'function')
|
||||
) {
|
||||
if (oldProps !== newProps || oldContext !== newContext) {
|
||||
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
|
||||
callComponentWillReceiveProps(
|
||||
workInProgress,
|
||||
instance,
|
||||
newProps,
|
||||
newContext,
|
||||
nextLegacyContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -934,6 +944,7 @@ function updateClassInstance(
|
||||
oldProps === newProps &&
|
||||
oldState === newState &&
|
||||
!hasContextChanged() &&
|
||||
!hasPendingNewContext &&
|
||||
!checkHasForceUpdateAfterProcessing()
|
||||
) {
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
@@ -968,13 +979,14 @@ function updateClassInstance(
|
||||
|
||||
const shouldUpdate =
|
||||
checkHasForceUpdateAfterProcessing() ||
|
||||
hasPendingNewContext ||
|
||||
checkShouldComponentUpdate(
|
||||
workInProgress,
|
||||
oldProps,
|
||||
newProps,
|
||||
oldState,
|
||||
newState,
|
||||
newContext,
|
||||
nextLegacyContext,
|
||||
);
|
||||
|
||||
if (shouldUpdate) {
|
||||
@@ -987,10 +999,14 @@ function updateClassInstance(
|
||||
) {
|
||||
startPhaseTimer(workInProgress, 'componentWillUpdate');
|
||||
if (typeof instance.componentWillUpdate === 'function') {
|
||||
instance.componentWillUpdate(newProps, newState, newContext);
|
||||
instance.componentWillUpdate(newProps, newState, nextLegacyContext);
|
||||
}
|
||||
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
|
||||
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
|
||||
instance.UNSAFE_componentWillUpdate(
|
||||
newProps,
|
||||
newState,
|
||||
nextLegacyContext,
|
||||
);
|
||||
}
|
||||
stopPhaseTimer();
|
||||
}
|
||||
@@ -1030,7 +1046,7 @@ function updateClassInstance(
|
||||
// if shouldComponentUpdate returns false.
|
||||
instance.props = newProps;
|
||||
instance.state = newState;
|
||||
instance.context = newContext;
|
||||
instance.context = nextLegacyContext;
|
||||
|
||||
return shouldUpdate;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {readContext} from './ReactFiberNewContext';
|
||||
|
||||
export const Dispatcher = {
|
||||
readContext,
|
||||
};
|
||||
+227
-24
@@ -7,22 +7,26 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {StackCursor} from './ReactFiberStack';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
|
||||
export type NewContext = {
|
||||
pushProvider(providerFiber: Fiber): void,
|
||||
popProvider(providerFiber: Fiber): void,
|
||||
getContextCurrentValue(context: ReactContext<any>): any,
|
||||
getContextChangedBits(context: ReactContext<any>): number,
|
||||
export type ContextDependency<T> = {
|
||||
context: ReactContext<T>,
|
||||
observedBits: number,
|
||||
next: ContextDependency<mixed> | null,
|
||||
};
|
||||
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import {isPrimaryRenderer} from './ReactFiberHostConfig';
|
||||
import {createCursor, push, pop} from './ReactFiberStack';
|
||||
import maxSigned31BitInt from './maxSigned31BitInt';
|
||||
import {NoWork} from './ReactFiberExpirationTime';
|
||||
import {ContextProvider} from 'shared/ReactTypeOfWork';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
|
||||
const valueCursor: StackCursor<mixed> = createCursor(null);
|
||||
const changedBitsCursor: StackCursor<number> = createCursor(0);
|
||||
|
||||
@@ -32,13 +36,24 @@ if (__DEV__) {
|
||||
rendererSigil = {};
|
||||
}
|
||||
|
||||
function pushProvider(providerFiber: Fiber): void {
|
||||
let currentlyRenderingFiber: Fiber | null = null;
|
||||
let lastContextDependency: ContextDependency<mixed> | null = null;
|
||||
let lastContext: ReactContext<any> | null = null;
|
||||
|
||||
export function resetContextDependences(): void {
|
||||
// This is called right before React yields execution, to ensure `readContext`
|
||||
// cannot be called outside the render phase.
|
||||
currentlyRenderingFiber = null;
|
||||
lastContextDependency = null;
|
||||
lastContext = null;
|
||||
}
|
||||
|
||||
export function pushProvider(providerFiber: Fiber): void {
|
||||
const context: ReactContext<any> = providerFiber.type._context;
|
||||
|
||||
if (isPrimaryRenderer) {
|
||||
push(changedBitsCursor, context._changedBits, providerFiber);
|
||||
push(valueCursor, context._currentValue, providerFiber);
|
||||
push(providerCursor, providerFiber, providerFiber);
|
||||
|
||||
context._currentValue = providerFiber.pendingProps.value;
|
||||
context._changedBits = providerFiber.stateNode;
|
||||
@@ -55,7 +70,6 @@ function pushProvider(providerFiber: Fiber): void {
|
||||
} else {
|
||||
push(changedBitsCursor, context._changedBits2, providerFiber);
|
||||
push(valueCursor, context._currentValue2, providerFiber);
|
||||
push(providerCursor, providerFiber, providerFiber);
|
||||
|
||||
context._currentValue2 = providerFiber.pendingProps.value;
|
||||
context._changedBits2 = providerFiber.stateNode;
|
||||
@@ -72,11 +86,10 @@ function pushProvider(providerFiber: Fiber): void {
|
||||
}
|
||||
}
|
||||
|
||||
function popProvider(providerFiber: Fiber): void {
|
||||
export function popProvider(providerFiber: Fiber): void {
|
||||
const changedBits = changedBitsCursor.current;
|
||||
const currentValue = valueCursor.current;
|
||||
|
||||
pop(providerCursor, providerFiber);
|
||||
pop(valueCursor, providerFiber);
|
||||
pop(changedBitsCursor, providerFiber);
|
||||
|
||||
@@ -90,17 +103,207 @@ function popProvider(providerFiber: Fiber): void {
|
||||
}
|
||||
}
|
||||
|
||||
function getContextCurrentValue(context: ReactContext<any>): any {
|
||||
export function propagateContextChange(
|
||||
workInProgress: Fiber,
|
||||
context: ReactContext<mixed>,
|
||||
changedBits: number,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): void {
|
||||
let fiber = workInProgress.child;
|
||||
if (fiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
fiber.return = workInProgress;
|
||||
}
|
||||
while (fiber !== null) {
|
||||
let nextFiber;
|
||||
|
||||
// Visit this fiber.
|
||||
let dependency = fiber.firstContextDependency;
|
||||
if (dependency !== null) {
|
||||
do {
|
||||
// Check if the context matches.
|
||||
if (
|
||||
dependency.context === context &&
|
||||
(dependency.observedBits & changedBits) !== 0
|
||||
) {
|
||||
// Match! Schedule an update on this fiber.
|
||||
if (
|
||||
fiber.expirationTime === NoWork ||
|
||||
fiber.expirationTime > renderExpirationTime
|
||||
) {
|
||||
fiber.expirationTime = renderExpirationTime;
|
||||
}
|
||||
let alternate = fiber.alternate;
|
||||
if (
|
||||
alternate !== null &&
|
||||
(alternate.expirationTime === NoWork ||
|
||||
alternate.expirationTime > renderExpirationTime)
|
||||
) {
|
||||
alternate.expirationTime = renderExpirationTime;
|
||||
}
|
||||
// Update the child expiration time of all the ancestors, including
|
||||
// the alternates.
|
||||
let node = fiber.return;
|
||||
while (node !== null) {
|
||||
alternate = node.alternate;
|
||||
if (
|
||||
node.childExpirationTime === NoWork ||
|
||||
node.childExpirationTime > renderExpirationTime
|
||||
) {
|
||||
node.childExpirationTime = renderExpirationTime;
|
||||
if (
|
||||
alternate !== null &&
|
||||
(alternate.childExpirationTime === NoWork ||
|
||||
alternate.childExpirationTime > renderExpirationTime)
|
||||
) {
|
||||
alternate.childExpirationTime = renderExpirationTime;
|
||||
}
|
||||
} else if (
|
||||
alternate !== null &&
|
||||
(alternate.childExpirationTime === NoWork ||
|
||||
alternate.childExpirationTime > renderExpirationTime)
|
||||
) {
|
||||
alternate.childExpirationTime = renderExpirationTime;
|
||||
} else {
|
||||
// Neither alternate was updated, which means the rest of the
|
||||
// ancestor path already has sufficient priority.
|
||||
break;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
// Don't scan deeper than a matching consumer. When we render the
|
||||
// consumer, we'll continue scanning from that point. This way the
|
||||
// scanning work is time-sliced.
|
||||
nextFiber = null;
|
||||
} else {
|
||||
nextFiber = fiber.child;
|
||||
}
|
||||
dependency = dependency.next;
|
||||
} while (dependency !== null);
|
||||
} else if (fiber.tag === ContextProvider) {
|
||||
// Don't scan deeper if this is a matching provider
|
||||
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
|
||||
} else {
|
||||
// Traverse down.
|
||||
nextFiber = fiber.child;
|
||||
}
|
||||
|
||||
if (nextFiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
nextFiber.return = fiber;
|
||||
} else {
|
||||
// No child. Traverse to next sibling.
|
||||
nextFiber = fiber;
|
||||
while (nextFiber !== null) {
|
||||
if (nextFiber === workInProgress) {
|
||||
// We're back to the root of this subtree. Exit.
|
||||
nextFiber = null;
|
||||
break;
|
||||
}
|
||||
let sibling = nextFiber.sibling;
|
||||
if (sibling !== null) {
|
||||
// Set the return pointer of the sibling to the work-in-progress fiber.
|
||||
sibling.return = nextFiber.return;
|
||||
nextFiber = sibling;
|
||||
break;
|
||||
}
|
||||
// No more siblings. Traverse up.
|
||||
nextFiber = nextFiber.return;
|
||||
}
|
||||
}
|
||||
fiber = nextFiber;
|
||||
}
|
||||
}
|
||||
|
||||
export function prepareToReadContext(
|
||||
workInProgress: Fiber,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): boolean {
|
||||
currentlyRenderingFiber = workInProgress;
|
||||
lastContextDependency = null;
|
||||
lastContext = null;
|
||||
|
||||
const firstContextDependency = workInProgress.firstContextDependency;
|
||||
if (firstContextDependency !== null) {
|
||||
// Reset the work-in-progress list
|
||||
workInProgress.firstContextDependency = null;
|
||||
|
||||
// Iterate through the context dependencies and see if there were any
|
||||
// changes. If so, continue propagating the context change by scanning
|
||||
// the child subtree.
|
||||
let dependency = firstContextDependency;
|
||||
let hasPendingContext = false;
|
||||
do {
|
||||
const context = dependency.context;
|
||||
const changedBits = isPrimaryRenderer
|
||||
? context._changedBits
|
||||
: context._changedBits2;
|
||||
if (changedBits !== 0) {
|
||||
// Resume context change propagation. We need to call this even if
|
||||
// this fiber bails out, in case deeply nested consumers observe more
|
||||
// bits than this one.
|
||||
propagateContextChange(
|
||||
workInProgress,
|
||||
context,
|
||||
changedBits,
|
||||
renderExpirationTime,
|
||||
);
|
||||
if ((changedBits & dependency.observedBits) !== 0) {
|
||||
hasPendingContext = true;
|
||||
}
|
||||
}
|
||||
dependency = dependency.next;
|
||||
} while (dependency !== null);
|
||||
return hasPendingContext;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
if (typeof observedBits !== 'number') {
|
||||
if (observedBits === false) {
|
||||
// Do not observe updates
|
||||
observedBits = 0;
|
||||
} else {
|
||||
// Observe all updates
|
||||
observedBits = maxSigned31BitInt;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastContext === null) {
|
||||
invariant(
|
||||
currentlyRenderingFiber !== null,
|
||||
'Context.unstable_read(): Context can only be read while React is ' +
|
||||
'rendering, e.g. inside the render method or getDerivedStateFromProps.',
|
||||
);
|
||||
// This is the first dependency in the list
|
||||
currentlyRenderingFiber.firstContextDependency = lastContextDependency = {
|
||||
context: ((context: any): ReactContext<mixed>),
|
||||
observedBits,
|
||||
next: null,
|
||||
};
|
||||
lastContext = context;
|
||||
} else {
|
||||
// `lastContextDependency` is always non-null if `lastContext is.
|
||||
const lastDependency: ContextDependency<T> = (lastContextDependency: any);
|
||||
if (lastContext === context) {
|
||||
// Fast path. The previous context has the same type. We can reuse
|
||||
// the same node.
|
||||
lastDependency.observedBits |= observedBits;
|
||||
} else {
|
||||
// Append a new context item.
|
||||
lastContextDependency = lastDependency.next = {
|
||||
context: ((context: any): ReactContext<mixed>),
|
||||
observedBits,
|
||||
next: null,
|
||||
};
|
||||
lastContext = context;
|
||||
}
|
||||
}
|
||||
|
||||
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
|
||||
}
|
||||
|
||||
function getContextChangedBits(context: ReactContext<any>): number {
|
||||
return isPrimaryRenderer ? context._changedBits : context._changedBits2;
|
||||
}
|
||||
|
||||
export {
|
||||
pushProvider,
|
||||
popProvider,
|
||||
getContextCurrentValue,
|
||||
getContextChangedBits,
|
||||
};
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ function scheduleRootUpdate(
|
||||
);
|
||||
update.callback = callback;
|
||||
}
|
||||
enqueueUpdate(current, update, expirationTime);
|
||||
enqueueUpdate(current, update);
|
||||
|
||||
scheduleWork(current, expirationTime);
|
||||
return expirationTime;
|
||||
|
||||
+93
-51
@@ -109,7 +109,7 @@ import {
|
||||
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
||||
popContextProvider as popLegacyContextProvider,
|
||||
} from './ReactFiberContext';
|
||||
import {popProvider} from './ReactFiberNewContext';
|
||||
import {popProvider, resetContextDependences} from './ReactFiberNewContext';
|
||||
import {popHostContext, popHostContainer} from './ReactFiberHostContext';
|
||||
import {
|
||||
checkActualRenderTimeStackEmpty,
|
||||
@@ -145,6 +145,7 @@ import {
|
||||
commitAttachRef,
|
||||
commitDetachRef,
|
||||
} from './ReactFiberCommitWork';
|
||||
import {Dispatcher} from './ReactFiberDispatcher';
|
||||
|
||||
export type Deadline = {
|
||||
timeRemaining: () => number,
|
||||
@@ -532,8 +533,15 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
|
||||
// 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.
|
||||
const earliestRemainingTime = finishedWork.expirationTime;
|
||||
markCommittedPriorityLevels(root, earliestRemainingTime);
|
||||
const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
|
||||
const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
|
||||
const earliestRemainingTimeBeforeCommit =
|
||||
updateExpirationTimeBeforeCommit === NoWork ||
|
||||
(childExpirationTimeBeforeCommit !== NoWork &&
|
||||
childExpirationTimeBeforeCommit < updateExpirationTimeBeforeCommit)
|
||||
? childExpirationTimeBeforeCommit
|
||||
: updateExpirationTimeBeforeCommit;
|
||||
markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);
|
||||
|
||||
// Reset this to null before calling lifecycles
|
||||
ReactCurrentOwner.current = null;
|
||||
@@ -705,71 +713,85 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
|
||||
ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
|
||||
}
|
||||
|
||||
const expirationTime = root.expirationTime;
|
||||
if (expirationTime === NoWork) {
|
||||
const updateExpirationTimeAfterCommit = finishedWork.expirationTime;
|
||||
const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;
|
||||
const earliestRemainingTimeAfterCommit =
|
||||
updateExpirationTimeAfterCommit === NoWork ||
|
||||
(childExpirationTimeAfterCommit !== NoWork &&
|
||||
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, expirationTime);
|
||||
onCommit(root, earliestRemainingTimeAfterCommit);
|
||||
}
|
||||
|
||||
function resetExpirationTime(
|
||||
function resetChildExpirationTime(
|
||||
workInProgress: Fiber,
|
||||
renderTime: ExpirationTime,
|
||||
) {
|
||||
if (renderTime !== Never && workInProgress.expirationTime === Never) {
|
||||
if (renderTime !== Never && workInProgress.childExpirationTime === Never) {
|
||||
// The children of this component are hidden. Don't bubble their
|
||||
// expiration times.
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for pending updates.
|
||||
let newExpirationTime = NoWork;
|
||||
switch (workInProgress.tag) {
|
||||
case HostRoot:
|
||||
case ClassComponent: {
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
newExpirationTime = updateQueue.expirationTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Calls need to visit stateNode
|
||||
let newChildExpirationTime = NoWork;
|
||||
|
||||
// Bubble up the earliest expiration time.
|
||||
// (And "base" render timers if that feature flag is enabled)
|
||||
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
|
||||
// We're in profiling mode. Let's use this same traversal to update the
|
||||
// "base" render times.
|
||||
let treeBaseDuration = workInProgress.selfBaseDuration;
|
||||
let child = workInProgress.child;
|
||||
while (child !== null) {
|
||||
treeBaseDuration += child.treeBaseDuration;
|
||||
const childUpdateExpirationTime = child.expirationTime;
|
||||
const childChildExpirationTime = child.childExpirationTime;
|
||||
if (
|
||||
child.expirationTime !== NoWork &&
|
||||
(newExpirationTime === NoWork ||
|
||||
newExpirationTime > child.expirationTime)
|
||||
newChildExpirationTime === NoWork ||
|
||||
(childUpdateExpirationTime !== NoWork &&
|
||||
childUpdateExpirationTime < newChildExpirationTime)
|
||||
) {
|
||||
newExpirationTime = child.expirationTime;
|
||||
newChildExpirationTime = childUpdateExpirationTime;
|
||||
}
|
||||
if (
|
||||
newChildExpirationTime === NoWork ||
|
||||
(childChildExpirationTime !== NoWork &&
|
||||
childChildExpirationTime < newChildExpirationTime)
|
||||
) {
|
||||
newChildExpirationTime = childChildExpirationTime;
|
||||
}
|
||||
treeBaseDuration += child.treeBaseDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
workInProgress.treeBaseDuration = treeBaseDuration;
|
||||
} else {
|
||||
let child = workInProgress.child;
|
||||
while (child !== null) {
|
||||
const childUpdateExpirationTime = child.expirationTime;
|
||||
const childChildExpirationTime = child.childExpirationTime;
|
||||
if (
|
||||
child.expirationTime !== NoWork &&
|
||||
(newExpirationTime === NoWork ||
|
||||
newExpirationTime > child.expirationTime)
|
||||
newChildExpirationTime === NoWork ||
|
||||
(childUpdateExpirationTime !== NoWork &&
|
||||
childUpdateExpirationTime < newChildExpirationTime)
|
||||
) {
|
||||
newExpirationTime = child.expirationTime;
|
||||
newChildExpirationTime = childUpdateExpirationTime;
|
||||
}
|
||||
if (
|
||||
newChildExpirationTime === NoWork ||
|
||||
(childChildExpirationTime !== NoWork &&
|
||||
childChildExpirationTime < newChildExpirationTime)
|
||||
) {
|
||||
newChildExpirationTime = childChildExpirationTime;
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
workInProgress.expirationTime = newExpirationTime;
|
||||
workInProgress.childExpirationTime = newChildExpirationTime;
|
||||
}
|
||||
|
||||
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
@@ -798,7 +820,7 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
);
|
||||
let next = nextUnitOfWork;
|
||||
stopWorkTimer(workInProgress);
|
||||
resetExpirationTime(workInProgress, nextRenderExpirationTime);
|
||||
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
|
||||
if (__DEV__) {
|
||||
ReactCurrentFiber.resetCurrentFiber();
|
||||
}
|
||||
@@ -1017,6 +1039,7 @@ function renderRoot(
|
||||
'by a bug in React. Please file an issue.',
|
||||
);
|
||||
isWorking = true;
|
||||
ReactCurrentOwner.currentDispatcher = Dispatcher;
|
||||
|
||||
const expirationTime = root.nextExpirationTimeToWorkOn;
|
||||
|
||||
@@ -1107,6 +1130,8 @@ function renderRoot(
|
||||
|
||||
// We're done performing work. Time to clean up.
|
||||
isWorking = false;
|
||||
ReactCurrentOwner.currentDispatcher = null;
|
||||
resetContextDependences();
|
||||
|
||||
// Yield back to main thread.
|
||||
if (didFatal) {
|
||||
@@ -1267,7 +1292,7 @@ function dispatch(
|
||||
errorInfo,
|
||||
expirationTime,
|
||||
);
|
||||
enqueueUpdate(fiber, update, expirationTime);
|
||||
enqueueUpdate(fiber, update);
|
||||
scheduleWork(fiber, expirationTime);
|
||||
return;
|
||||
}
|
||||
@@ -1275,7 +1300,7 @@ function dispatch(
|
||||
case HostRoot: {
|
||||
const errorInfo = createCapturedValue(value, sourceFiber);
|
||||
const update = createRootErrorUpdate(fiber, errorInfo, expirationTime);
|
||||
enqueueUpdate(fiber, update, expirationTime);
|
||||
enqueueUpdate(fiber, update);
|
||||
scheduleWork(fiber, expirationTime);
|
||||
return;
|
||||
}
|
||||
@@ -1289,7 +1314,7 @@ function dispatch(
|
||||
const rootFiber = sourceFiber;
|
||||
const errorInfo = createCapturedValue(value, rootFiber);
|
||||
const update = createRootErrorUpdate(rootFiber, errorInfo, expirationTime);
|
||||
enqueueUpdate(rootFiber, update, expirationTime);
|
||||
enqueueUpdate(rootFiber, update);
|
||||
scheduleWork(rootFiber, expirationTime);
|
||||
}
|
||||
}
|
||||
@@ -1408,35 +1433,52 @@ function retrySuspendedRoot(
|
||||
}
|
||||
|
||||
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
|
||||
// Walk the parent path to the root and update each node's
|
||||
// expiration time.
|
||||
let node = fiber;
|
||||
do {
|
||||
const alternate = node.alternate;
|
||||
// Update the source fiber's expiration time
|
||||
if (
|
||||
fiber.expirationTime === NoWork ||
|
||||
fiber.expirationTime > expirationTime
|
||||
) {
|
||||
fiber.expirationTime = expirationTime;
|
||||
}
|
||||
let alternate = fiber.alternate;
|
||||
if (
|
||||
alternate !== null &&
|
||||
(alternate.expirationTime === NoWork ||
|
||||
alternate.expirationTime > expirationTime)
|
||||
) {
|
||||
alternate.expirationTime = expirationTime;
|
||||
}
|
||||
// Walk the parent path to the root and update the child expiration time.
|
||||
let node = fiber.return;
|
||||
if (node === null && fiber.tag === HostRoot) {
|
||||
return fiber.stateNode;
|
||||
}
|
||||
while (node !== null) {
|
||||
alternate = node.alternate;
|
||||
if (
|
||||
node.expirationTime === NoWork ||
|
||||
node.expirationTime > expirationTime
|
||||
node.childExpirationTime === NoWork ||
|
||||
node.childExpirationTime > expirationTime
|
||||
) {
|
||||
node.expirationTime = expirationTime;
|
||||
node.childExpirationTime = expirationTime;
|
||||
if (
|
||||
alternate !== null &&
|
||||
(alternate.expirationTime === NoWork ||
|
||||
alternate.expirationTime > expirationTime)
|
||||
(alternate.childExpirationTime === NoWork ||
|
||||
alternate.childExpirationTime > expirationTime)
|
||||
) {
|
||||
alternate.expirationTime = expirationTime;
|
||||
alternate.childExpirationTime = expirationTime;
|
||||
}
|
||||
} else if (
|
||||
alternate !== null &&
|
||||
(alternate.expirationTime === NoWork ||
|
||||
alternate.expirationTime > expirationTime)
|
||||
(alternate.childExpirationTime === NoWork ||
|
||||
alternate.childExpirationTime > expirationTime)
|
||||
) {
|
||||
alternate.expirationTime = expirationTime;
|
||||
alternate.childExpirationTime = expirationTime;
|
||||
}
|
||||
if (node.return === null && node.tag === HostRoot) {
|
||||
return node.stateNode;
|
||||
}
|
||||
node = node.return;
|
||||
} while (node !== null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+7
-4
@@ -70,7 +70,7 @@ import {
|
||||
LOW_PRIORITY_EXPIRATION,
|
||||
} from './ReactFiberExpirationTime';
|
||||
import {findEarliestOutstandingPriorityLevel} from './ReactFiberPendingPriority';
|
||||
import {reconcileChildrenAtExpirationTime} from './ReactFiberBeginWork';
|
||||
import {reconcileChildren} from './ReactFiberBeginWork';
|
||||
|
||||
function NoopComponent() {
|
||||
return null;
|
||||
@@ -238,7 +238,7 @@ function throwException(
|
||||
|
||||
// Unmount the source fiber's children
|
||||
const nextChildren = null;
|
||||
reconcileChildrenAtExpirationTime(
|
||||
reconcileChildren(
|
||||
sourceFiber.alternate,
|
||||
sourceFiber,
|
||||
nextChildren,
|
||||
@@ -310,6 +310,7 @@ function throwException(
|
||||
renderDidSuspend(root, absoluteTimeoutMs, renderExpirationTime);
|
||||
|
||||
workInProgress.effectTag |= ShouldCapture;
|
||||
workInProgress.expirationTime = renderExpirationTime;
|
||||
return;
|
||||
}
|
||||
// This boundary already captured during this render. Continue to the
|
||||
@@ -334,12 +335,13 @@ function throwException(
|
||||
case HostRoot: {
|
||||
const errorInfo = value;
|
||||
workInProgress.effectTag |= ShouldCapture;
|
||||
workInProgress.expirationTime = renderExpirationTime;
|
||||
const update = createRootErrorUpdate(
|
||||
workInProgress,
|
||||
errorInfo,
|
||||
renderExpirationTime,
|
||||
);
|
||||
enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
|
||||
enqueueCapturedUpdate(workInProgress, update);
|
||||
return;
|
||||
}
|
||||
case ClassComponent:
|
||||
@@ -356,13 +358,14 @@ function throwException(
|
||||
!isAlreadyFailedLegacyErrorBoundary(instance)))
|
||||
) {
|
||||
workInProgress.effectTag |= ShouldCapture;
|
||||
workInProgress.expirationTime = renderExpirationTime;
|
||||
// Schedule the error boundary to re-render using updated state
|
||||
const update = createClassErrorUpdate(
|
||||
workInProgress,
|
||||
errorInfo,
|
||||
renderExpirationTime,
|
||||
);
|
||||
enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
|
||||
enqueueCapturedUpdate(workInProgress, update);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
+13
-39
@@ -117,7 +117,6 @@ export type Update<State> = {
|
||||
};
|
||||
|
||||
export type UpdateQueue<State> = {
|
||||
expirationTime: ExpirationTime,
|
||||
baseState: State,
|
||||
|
||||
firstUpdate: Update<State> | null,
|
||||
@@ -156,7 +155,6 @@ if (__DEV__) {
|
||||
|
||||
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
|
||||
const queue: UpdateQueue<State> = {
|
||||
expirationTime: NoWork,
|
||||
baseState,
|
||||
firstUpdate: null,
|
||||
lastUpdate: null,
|
||||
@@ -174,7 +172,6 @@ function cloneUpdateQueue<State>(
|
||||
currentQueue: UpdateQueue<State>,
|
||||
): UpdateQueue<State> {
|
||||
const queue: UpdateQueue<State> = {
|
||||
expirationTime: currentQueue.expirationTime,
|
||||
baseState: currentQueue.baseState,
|
||||
firstUpdate: currentQueue.firstUpdate,
|
||||
lastUpdate: currentQueue.lastUpdate,
|
||||
@@ -209,7 +206,6 @@ export function createUpdate(expirationTime: ExpirationTime): Update<*> {
|
||||
function appendUpdateToQueue<State>(
|
||||
queue: UpdateQueue<State>,
|
||||
update: Update<State>,
|
||||
expirationTime: ExpirationTime,
|
||||
) {
|
||||
// Append the update to the end of the list.
|
||||
if (queue.lastUpdate === null) {
|
||||
@@ -219,21 +215,9 @@ function appendUpdateToQueue<State>(
|
||||
queue.lastUpdate.next = update;
|
||||
queue.lastUpdate = update;
|
||||
}
|
||||
if (
|
||||
queue.expirationTime === NoWork ||
|
||||
queue.expirationTime > expirationTime
|
||||
) {
|
||||
// The incoming update has the earliest expiration of any update in the
|
||||
// queue. Update the queue's expiration time.
|
||||
queue.expirationTime = expirationTime;
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueUpdate<State>(
|
||||
fiber: Fiber,
|
||||
update: Update<State>,
|
||||
expirationTime: ExpirationTime,
|
||||
) {
|
||||
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
|
||||
// Update queues are created lazily.
|
||||
const alternate = fiber.alternate;
|
||||
let queue1;
|
||||
@@ -271,19 +255,19 @@ export function enqueueUpdate<State>(
|
||||
}
|
||||
if (queue2 === null || queue1 === queue2) {
|
||||
// There's only a single queue.
|
||||
appendUpdateToQueue(queue1, update, expirationTime);
|
||||
appendUpdateToQueue(queue1, update);
|
||||
} else {
|
||||
// There are two queues. We need to append the update to both queues,
|
||||
// while accounting for the persistent structure of the list — we don't
|
||||
// want the same update to be added multiple times.
|
||||
if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
|
||||
// One of the queues is not empty. We must add the update to both queues.
|
||||
appendUpdateToQueue(queue1, update, expirationTime);
|
||||
appendUpdateToQueue(queue2, update, expirationTime);
|
||||
appendUpdateToQueue(queue1, update);
|
||||
appendUpdateToQueue(queue2, update);
|
||||
} else {
|
||||
// Both queues are non-empty. The last update is the same in both lists,
|
||||
// because of structural sharing. So, only append to one of the lists.
|
||||
appendUpdateToQueue(queue1, update, expirationTime);
|
||||
appendUpdateToQueue(queue1, update);
|
||||
// But we still need to update the `lastUpdate` pointer of queue2.
|
||||
queue2.lastUpdate = update;
|
||||
}
|
||||
@@ -311,7 +295,6 @@ export function enqueueUpdate<State>(
|
||||
export function enqueueCapturedUpdate<State>(
|
||||
workInProgress: Fiber,
|
||||
update: Update<State>,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
) {
|
||||
// Captured updates go into a separate list, and only on the work-in-
|
||||
// progress queue.
|
||||
@@ -338,14 +321,6 @@ export function enqueueCapturedUpdate<State>(
|
||||
workInProgressQueue.lastCapturedUpdate.next = update;
|
||||
workInProgressQueue.lastCapturedUpdate = update;
|
||||
}
|
||||
if (
|
||||
workInProgressQueue.expirationTime === NoWork ||
|
||||
workInProgressQueue.expirationTime > renderExpirationTime
|
||||
) {
|
||||
// The incoming update has the earliest expiration of any update in the
|
||||
// queue. Update the queue's expiration time.
|
||||
workInProgressQueue.expirationTime = renderExpirationTime;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureWorkInProgressQueueIsAClone<State>(
|
||||
@@ -438,14 +413,6 @@ export function processUpdateQueue<State>(
|
||||
): void {
|
||||
hasForceUpdate = false;
|
||||
|
||||
if (
|
||||
queue.expirationTime === NoWork ||
|
||||
queue.expirationTime > renderExpirationTime
|
||||
) {
|
||||
// Insufficient priority. Bailout.
|
||||
return;
|
||||
}
|
||||
|
||||
queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);
|
||||
|
||||
if (__DEV__) {
|
||||
@@ -577,8 +544,15 @@ export function processUpdateQueue<State>(
|
||||
queue.baseState = newBaseState;
|
||||
queue.firstUpdate = newFirstUpdate;
|
||||
queue.firstCapturedUpdate = newFirstCapturedUpdate;
|
||||
queue.expirationTime = newExpirationTime;
|
||||
|
||||
// Set the remaining expiration time to be whatever is remaining in the queue.
|
||||
// This should be fine because the only two other things that contribute to
|
||||
// expiration time are props and context. We're already in the middle of the
|
||||
// begin phase by the time we start processing the queue, so we've already
|
||||
// dealt with the props. Context in components that specify
|
||||
// shouldComponentUpdate is tricky; but we'll have to account for
|
||||
// that regardless.
|
||||
workInProgress.expirationTime = newExpirationTime;
|
||||
workInProgress.memoizedState = resultState;
|
||||
|
||||
if (__DEV__) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,24 @@ import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
import ReactCurrentOwner from './ReactCurrentOwner';
|
||||
|
||||
export function readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
const dispatcher = ReactCurrentOwner.currentDispatcher;
|
||||
invariant(
|
||||
dispatcher !== null,
|
||||
'Context.unstable_read(): Context can only be read while React is ' +
|
||||
'rendering, e.g. inside the render method or getDerivedStateFromProps.',
|
||||
);
|
||||
return dispatcher.readContext(context, observedBits);
|
||||
}
|
||||
|
||||
export function createContext<T>(
|
||||
defaultValue: T,
|
||||
calculateChangedBits: ?(a: T, b: T) => number,
|
||||
@@ -47,6 +63,7 @@ export function createContext<T>(
|
||||
// These are circular
|
||||
Provider: (null: any),
|
||||
Consumer: (null: any),
|
||||
unstable_read: (null: any),
|
||||
};
|
||||
|
||||
context.Provider = {
|
||||
@@ -54,6 +71,7 @@ export function createContext<T>(
|
||||
_context: context,
|
||||
};
|
||||
context.Consumer = context;
|
||||
context.unstable_read = readContext.bind(null, context);
|
||||
|
||||
if (__DEV__) {
|
||||
context._currentRenderer = null;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import typeof {Dispatcher} from 'react-reconciler/src/ReactFiberDispatcher';
|
||||
|
||||
/**
|
||||
* Keeps track of the current owner.
|
||||
@@ -21,6 +22,7 @@ const ReactCurrentOwner = {
|
||||
* @type {ReactComponent}
|
||||
*/
|
||||
current: (null: null | Fiber),
|
||||
currentDispatcher: (null: null | Dispatcher),
|
||||
};
|
||||
|
||||
export default ReactCurrentOwner;
|
||||
|
||||
@@ -79,6 +79,7 @@ export type ReactContext<T> = {
|
||||
$$typeof: Symbol | number,
|
||||
Consumer: ReactContext<T>,
|
||||
Provider: ReactProviderType<T>,
|
||||
unstable_read: () => T,
|
||||
|
||||
_calculateChangedBits: ((a: T, b: T) => number) | null,
|
||||
_defaultValue: T,
|
||||
|
||||
Reference in New Issue
Block a user