Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blocks] Make it possible to have lazy initialized and lazy loaded Blocks #18220

Merged
merged 3 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import type {ReactElement} from 'shared/ReactElementType';
import type {ReactPortal} from 'shared/ReactTypes';
import type {BlockComponent} from 'react/src/block';
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';

Expand Down Expand Up @@ -47,6 +48,7 @@ import {
} from './ReactCurrentFiber';
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
import {StrictMode} from './ReactTypeOfMode';
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';

let didWarnAboutMaps;
let didWarnAboutGenerators;
Expand Down Expand Up @@ -420,18 +422,25 @@ function ChildReconciler(shouldTrackSideEffects) {
} else if (
enableBlocksAPI &&
current.tag === Block &&
element.type.$$typeof === REACT_BLOCK_TYPE &&
element.type.render === current.type.render
element.type.$$typeof === REACT_BLOCK_TYPE
) {
// Same as above but also update the .type field.
const existing = useFiber(current, element.props);
existing.return = returnFiber;
existing.type = element.type;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
// The new Block might not be initialized yet. We need to initialize
// it in case initializing it turns out it would match.
initializeBlockComponentType(element.type);
if (
(element.type: BlockComponent<any, any, any>)._fn ===
(current.type: BlockComponent<any, any, any>)._fn
) {
// Same as above but also update the .type field.
const existing = useFiber(current, element.props);
existing.return = returnFiber;
existing.type = element.type;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
return existing;
}
}
// Insert
Expand Down Expand Up @@ -1179,19 +1188,24 @@ function ChildReconciler(shouldTrackSideEffects) {
}
case Block:
if (enableBlocksAPI) {
if (
element.type.$$typeof === REACT_BLOCK_TYPE &&
element.type.render === child.type.render
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.type = element.type;
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
if (element.type.$$typeof === REACT_BLOCK_TYPE) {
// The new Block might not be initialized yet. We need to initialize
// it in case initializing it turns out it would match.
initializeBlockComponentType(element.type);
if (
(element.type: BlockComponent<any, any, any>)._fn ===
(child.type: BlockComponent<any, any, any>)._fn
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.type = element.type;
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
return existing;
}
}
// We intentionally fallthrough here if enableBlocksAPI is not on.
Expand Down
17 changes: 13 additions & 4 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
import type {BlockComponent} from 'react/src/block';
import type {Fiber} from './ReactFiber';
import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
Expand All @@ -17,6 +18,7 @@ import type {
SuspenseListTailMode,
} from './ReactFiberSuspenseComponent';
import type {SuspenseContext} from './ReactFiberSuspenseContext';
import type {} from 'react/src/block';
gaearon marked this conversation as resolved.
Show resolved Hide resolved

import checkPropTypes from 'shared/checkPropTypes';

Expand Down Expand Up @@ -166,6 +168,7 @@ import {
readLazyComponentType,
resolveDefaultProps,
} from './ReactFiberLazyComponent';
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
import {
resolveLazyComponentTag,
createFiberFromTypeAndProps,
Expand All @@ -181,6 +184,7 @@ import {
renderDidSuspendDelayIfPossible,
markUnprocessedUpdateTime,
} from './ReactFiberWorkLoop';
import {Resolved} from 'shared/ReactLazyStatusTags';

const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;

Expand Down Expand Up @@ -693,19 +697,24 @@ function updateFunctionComponent(
return workInProgress.child;
}

function updateBlock(
function updateBlock<Props, Payload, Data>(
current: Fiber | null,
workInProgress: Fiber,
block: any,
block: BlockComponent<Props, Payload, Data>,
nextProps: any,
renderExpirationTime: ExpirationTime,
) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.

const render = block.render;
const data = block.query();
initializeBlockComponentType(block);
if (block._status !== Resolved) {
throw block._data;
}

const render = block._fn;
const data = block._data;

// The rest is a fork of updateFunctionComponent
let nextChildren;
Expand Down
8 changes: 4 additions & 4 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,12 @@ function areHookInputsEqual(
return true;
}

export function renderWithHooks(
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
secondArg: any,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderExpirationTime: ExpirationTime,
): any {
renderExpirationTime = nextRenderExpirationTime;
Expand Down
75 changes: 66 additions & 9 deletions packages/react/src/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import {
Expand All @@ -11,14 +13,64 @@ import {
REACT_FORWARD_REF_TYPE,
} from 'shared/ReactSymbols';

type BlockQueryFunction<Args: Iterable<any>, Data> = (...args: Args) => Data;
type BlockRenderFunction<Props, Data> = (
props: Props,
data: Data,
) => React$Node;

type Thenable<T, R> = {
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
};

type Initializer<Props, Payload, Data> = (
payload: Payload,
) =>
| [Data, BlockRenderFunction<Props, Data>]
| Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>;

export type UninitializedBlockComponent<Props, Payload, Data> = {
$$typeof: Symbol | number,
_status: -1,
_data: Payload,
_fn: Initializer<Props, Payload, Data>,
};

export type PendingBlockComponent<Props, Data> = {
$$typeof: Symbol | number,
_status: 0,
_data: Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>,
_fn: null,
};

export type ResolvedBlockComponent<Props, Data> = {
$$typeof: Symbol | number,
_status: 1,
_data: Data,
_fn: BlockRenderFunction<Props, Data>,
};

export type RejectedBlockComponent = {
$$typeof: Symbol | number,
_status: 2,
_data: mixed,
_fn: null,
};

export type BlockComponent<Props, Payload, Data> =
| UninitializedBlockComponent<Props, Payload, Data>
| PendingBlockComponent<Props, Data>
| ResolvedBlockComponent<Props, Data>
| RejectedBlockComponent;

opaque type Block<Props>: React$AbstractComponent<
Props,
null,
> = React$AbstractComponent<Props, null>;

export default function block<Args, Props, Data>(
query: (...args: Args) => Data,
render: (props: Props, data: Data) => React$Node,
export default function block<Args: Iterable<any>, Props, Data>(
query: BlockQueryFunction<Args, Data>,
render: BlockRenderFunction<Props, Data>,
): (...args: Args) => Block<Props> {
if (__DEV__) {
if (typeof query !== 'function') {
Expand Down Expand Up @@ -63,14 +115,19 @@ export default function block<Args, Props, Data>(
);
}
}
function initializer(args) {
let data = query.apply(null, args);
return [data, render];
}
return function(): Block<Props> {
let args = arguments;
return {
let args: Args = arguments;
let blockComponent: UninitializedBlockComponent<Props, Args, Data> = {
$$typeof: REACT_BLOCK_TYPE,
query: function() {
return query.apply(null, args);
},
render: render,
_status: -1,
_data: args,
_fn: initializer,
};
// $FlowFixMe
return blockComponent;
};
}
54 changes: 54 additions & 0 deletions packages/shared/ReactLazyComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import type {
LazyComponent,
} from 'react/src/ReactLazy';

import type {
PendingBlockComponent,
ResolvedBlockComponent,
RejectedBlockComponent,
BlockComponent,
} from 'react/src/block';

import {
Uninitialized,
Pending,
Expand Down Expand Up @@ -72,3 +79,50 @@ export function initializeLazyComponentType(
);
}
}

export function initializeBlockComponentType<Props, Payload, Data>(
blockComponent: BlockComponent<Props, Payload, Data>,
): void {
if (blockComponent._status === Uninitialized) {
const thenableOrTuple = blockComponent._fn(blockComponent._data);
if (typeof thenableOrTuple.then !== 'function') {
let tuple: [any, any] = (thenableOrTuple: any);
const resolved: ResolvedBlockComponent<
Props,
Data,
> = (blockComponent: any);
resolved._status = Resolved;
resolved._data = tuple[0];
resolved._fn = tuple[1];
return;
}
const thenable = (thenableOrTuple: any);
// Transition to the next state.
const pending: PendingBlockComponent<Props, Data> = (blockComponent: any);
pending._status = Pending;
pending._data = thenable;
pending._fn = null;
thenable.then(
(tuple: [any, any]) => {
if (blockComponent._status === Pending) {
// Transition to the next state.
const resolved: ResolvedBlockComponent<
Props,
Data,
> = (blockComponent: any);
resolved._status = Resolved;
resolved._data = tuple[0];
resolved._fn = tuple[1];
}
},
error => {
if (blockComponent._status === Pending) {
// Transition to the next state.
const rejected: RejectedBlockComponent = (blockComponent: any);
rejected._status = Rejected;
rejected._data = error;
}
},
);
}
}