Skip to content

Commit

Permalink
[Experimental] API for reading context from within any render phase f…
Browse files Browse the repository at this point in the history
…unction (#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
  • Loading branch information
acdlite committed Jul 20, 2018
1 parent 5776fa3 commit 2b509e2
Show file tree
Hide file tree
Showing 14 changed files with 1,842 additions and 1,468 deletions.
5 changes: 4 additions & 1 deletion packages/react-noop-renderer/src/createReactNoop.js
Expand Up @@ -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);
Expand Down
24 changes: 22 additions & 2 deletions packages/react-reconciler/src/ReactFiber.js
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -213,6 +220,7 @@ function FiberNode(
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.firstContextDependency = null;

this.mode = mode;

Expand All @@ -224,6 +232,7 @@ function FiberNode(
this.lastEffect = null;

this.expirationTime = NoWork;
this.childExpirationTime = NoWork;

this.alternate = null;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 2b509e2

Please sign in to comment.