Skip to content

Commit 238b57f

Browse files
authored
[Blocks] Make it possible to have lazy initialized and lazy loaded Blocks (#18220)
* Lazify Blocks Blocks now initialize lazily. * Initialize Blocks eagerly in ChildFiber This is for the case when it's a new Block that hasn't yet initialized. We need to first initialize it to see what "render function" it resolves to so that we can use that in our comparison. * Remove extra import type line
1 parent 235a6c4 commit 238b57f

File tree

5 files changed

+172
-39
lines changed

5 files changed

+172
-39
lines changed

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {ReactElement} from 'shared/ReactElementType';
1111
import type {ReactPortal} from 'shared/ReactTypes';
12+
import type {BlockComponent} from 'react/src/block';
1213
import type {Fiber} from './ReactFiber';
1314
import type {ExpirationTime} from './ReactFiberExpirationTime';
1415

@@ -47,6 +48,7 @@ import {
4748
} from './ReactCurrentFiber';
4849
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
4950
import {StrictMode} from './ReactTypeOfMode';
51+
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
5052

5153
let didWarnAboutMaps;
5254
let didWarnAboutGenerators;
@@ -420,18 +422,25 @@ function ChildReconciler(shouldTrackSideEffects) {
420422
} else if (
421423
enableBlocksAPI &&
422424
current.tag === Block &&
423-
element.type.$$typeof === REACT_BLOCK_TYPE &&
424-
element.type.render === current.type.render
425+
element.type.$$typeof === REACT_BLOCK_TYPE
425426
) {
426-
// Same as above but also update the .type field.
427-
const existing = useFiber(current, element.props);
428-
existing.return = returnFiber;
429-
existing.type = element.type;
430-
if (__DEV__) {
431-
existing._debugSource = element._source;
432-
existing._debugOwner = element._owner;
427+
// The new Block might not be initialized yet. We need to initialize
428+
// it in case initializing it turns out it would match.
429+
initializeBlockComponentType(element.type);
430+
if (
431+
(element.type: BlockComponent<any, any, any>)._fn ===
432+
(current.type: BlockComponent<any, any, any>)._fn
433+
) {
434+
// Same as above but also update the .type field.
435+
const existing = useFiber(current, element.props);
436+
existing.return = returnFiber;
437+
existing.type = element.type;
438+
if (__DEV__) {
439+
existing._debugSource = element._source;
440+
existing._debugOwner = element._owner;
441+
}
442+
return existing;
433443
}
434-
return existing;
435444
}
436445
}
437446
// Insert
@@ -1179,19 +1188,24 @@ function ChildReconciler(shouldTrackSideEffects) {
11791188
}
11801189
case Block:
11811190
if (enableBlocksAPI) {
1182-
if (
1183-
element.type.$$typeof === REACT_BLOCK_TYPE &&
1184-
element.type.render === child.type.render
1185-
) {
1186-
deleteRemainingChildren(returnFiber, child.sibling);
1187-
const existing = useFiber(child, element.props);
1188-
existing.type = element.type;
1189-
existing.return = returnFiber;
1190-
if (__DEV__) {
1191-
existing._debugSource = element._source;
1192-
existing._debugOwner = element._owner;
1191+
if (element.type.$$typeof === REACT_BLOCK_TYPE) {
1192+
// The new Block might not be initialized yet. We need to initialize
1193+
// it in case initializing it turns out it would match.
1194+
initializeBlockComponentType(element.type);
1195+
if (
1196+
(element.type: BlockComponent<any, any, any>)._fn ===
1197+
(child.type: BlockComponent<any, any, any>)._fn
1198+
) {
1199+
deleteRemainingChildren(returnFiber, child.sibling);
1200+
const existing = useFiber(child, element.props);
1201+
existing.type = element.type;
1202+
existing.return = returnFiber;
1203+
if (__DEV__) {
1204+
existing._debugSource = element._source;
1205+
existing._debugOwner = element._owner;
1206+
}
1207+
return existing;
11931208
}
1194-
return existing;
11951209
}
11961210
}
11971211
// We intentionally fallthrough here if enableBlocksAPI is not on.

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
11+
import type {BlockComponent} from 'react/src/block';
1112
import type {Fiber} from './ReactFiber';
1213
import type {FiberRoot} from './ReactFiberRoot';
1314
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -167,6 +168,7 @@ import {
167168
readLazyComponentType,
168169
resolveDefaultProps,
169170
} from './ReactFiberLazyComponent';
171+
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
170172
import {
171173
resolveLazyComponentTag,
172174
createFiberFromTypeAndProps,
@@ -182,6 +184,7 @@ import {
182184
renderDidSuspendDelayIfPossible,
183185
markUnprocessedUpdateTime,
184186
} from './ReactFiberWorkLoop';
187+
import {Resolved} from 'shared/ReactLazyStatusTags';
185188

186189
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
187190

@@ -700,19 +703,24 @@ function updateFunctionComponent(
700703
return workInProgress.child;
701704
}
702705

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

714-
const render = block.render;
715-
const data = block.query();
717+
initializeBlockComponentType(block);
718+
if (block._status !== Resolved) {
719+
throw block._data;
720+
}
721+
722+
const render = block._fn;
723+
const data = block._data;
716724

717725
// The rest is a fork of updateFunctionComponent
718726
let nextChildren;

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,12 @@ function areHookInputsEqual(
343343
return true;
344344
}
345345

346-
export function renderWithHooks(
346+
export function renderWithHooks<Props, SecondArg>(
347347
current: Fiber | null,
348348
workInProgress: Fiber,
349-
Component: any,
350-
props: any,
351-
secondArg: any,
349+
Component: (p: Props, arg: SecondArg) => any,
350+
props: Props,
351+
secondArg: SecondArg,
352352
nextRenderExpirationTime: ExpirationTime,
353353
): any {
354354
renderExpirationTime = nextRenderExpirationTime;

packages/react/src/block.js

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
*
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
68
*/
79

810
import {
@@ -11,14 +13,64 @@ import {
1113
REACT_FORWARD_REF_TYPE,
1214
} from 'shared/ReactSymbols';
1315

16+
type BlockQueryFunction<Args: Iterable<any>, Data> = (...args: Args) => Data;
17+
type BlockRenderFunction<Props, Data> = (
18+
props: Props,
19+
data: Data,
20+
) => React$Node;
21+
22+
type Thenable<T, R> = {
23+
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
24+
};
25+
26+
type Initializer<Props, Payload, Data> = (
27+
payload: Payload,
28+
) =>
29+
| [Data, BlockRenderFunction<Props, Data>]
30+
| Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>;
31+
32+
export type UninitializedBlockComponent<Props, Payload, Data> = {
33+
$$typeof: Symbol | number,
34+
_status: -1,
35+
_data: Payload,
36+
_fn: Initializer<Props, Payload, Data>,
37+
};
38+
39+
export type PendingBlockComponent<Props, Data> = {
40+
$$typeof: Symbol | number,
41+
_status: 0,
42+
_data: Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>,
43+
_fn: null,
44+
};
45+
46+
export type ResolvedBlockComponent<Props, Data> = {
47+
$$typeof: Symbol | number,
48+
_status: 1,
49+
_data: Data,
50+
_fn: BlockRenderFunction<Props, Data>,
51+
};
52+
53+
export type RejectedBlockComponent = {
54+
$$typeof: Symbol | number,
55+
_status: 2,
56+
_data: mixed,
57+
_fn: null,
58+
};
59+
60+
export type BlockComponent<Props, Payload, Data> =
61+
| UninitializedBlockComponent<Props, Payload, Data>
62+
| PendingBlockComponent<Props, Data>
63+
| ResolvedBlockComponent<Props, Data>
64+
| RejectedBlockComponent;
65+
1466
opaque type Block<Props>: React$AbstractComponent<
1567
Props,
1668
null,
1769
> = React$AbstractComponent<Props, null>;
1870

19-
export default function block<Args, Props, Data>(
20-
query: (...args: Args) => Data,
21-
render: (props: Props, data: Data) => React$Node,
71+
export default function block<Args: Iterable<any>, Props, Data>(
72+
query: BlockQueryFunction<Args, Data>,
73+
render: BlockRenderFunction<Props, Data>,
2274
): (...args: Args) => Block<Props> {
2375
if (__DEV__) {
2476
if (typeof query !== 'function') {
@@ -63,14 +115,19 @@ export default function block<Args, Props, Data>(
63115
);
64116
}
65117
}
118+
function initializer(args) {
119+
let data = query.apply(null, args);
120+
return [data, render];
121+
}
66122
return function(): Block<Props> {
67-
let args = arguments;
68-
return {
123+
let args: Args = arguments;
124+
let blockComponent: UninitializedBlockComponent<Props, Args, Data> = {
69125
$$typeof: REACT_BLOCK_TYPE,
70-
query: function() {
71-
return query.apply(null, args);
72-
},
73-
render: render,
126+
_status: -1,
127+
_data: args,
128+
_fn: initializer,
74129
};
130+
// $FlowFixMe
131+
return blockComponent;
75132
};
76133
}

packages/shared/ReactLazyComponent.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import type {
1414
LazyComponent,
1515
} from 'react/src/ReactLazy';
1616

17+
import type {
18+
PendingBlockComponent,
19+
ResolvedBlockComponent,
20+
RejectedBlockComponent,
21+
BlockComponent,
22+
} from 'react/src/block';
23+
1724
import {
1825
Uninitialized,
1926
Pending,
@@ -74,3 +81,50 @@ export function initializeLazyComponentType(
7481
);
7582
}
7683
}
84+
85+
export function initializeBlockComponentType<Props, Payload, Data>(
86+
blockComponent: BlockComponent<Props, Payload, Data>,
87+
): void {
88+
if (blockComponent._status === Uninitialized) {
89+
const thenableOrTuple = blockComponent._fn(blockComponent._data);
90+
if (typeof thenableOrTuple.then !== 'function') {
91+
let tuple: [any, any] = (thenableOrTuple: any);
92+
const resolved: ResolvedBlockComponent<
93+
Props,
94+
Data,
95+
> = (blockComponent: any);
96+
resolved._status = Resolved;
97+
resolved._data = tuple[0];
98+
resolved._fn = tuple[1];
99+
return;
100+
}
101+
const thenable = (thenableOrTuple: any);
102+
// Transition to the next state.
103+
const pending: PendingBlockComponent<Props, Data> = (blockComponent: any);
104+
pending._status = Pending;
105+
pending._data = thenable;
106+
pending._fn = null;
107+
thenable.then(
108+
(tuple: [any, any]) => {
109+
if (blockComponent._status === Pending) {
110+
// Transition to the next state.
111+
const resolved: ResolvedBlockComponent<
112+
Props,
113+
Data,
114+
> = (blockComponent: any);
115+
resolved._status = Resolved;
116+
resolved._data = tuple[0];
117+
resolved._fn = tuple[1];
118+
}
119+
},
120+
error => {
121+
if (blockComponent._status === Pending) {
122+
// Transition to the next state.
123+
const rejected: RejectedBlockComponent = (blockComponent: any);
124+
rejected._status = Rejected;
125+
rejected._data = error;
126+
}
127+
},
128+
);
129+
}
130+
}

0 commit comments

Comments
 (0)