[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
Sebastian Markbåge committed
Mar 6, 2020 at 15:14 UTC
238b57f0f712aeddc89604b0dc9c1b13ffb0129a
5 files changed
+172
-39
packages/react-reconciler/src/ReactChildFiber.js
+36
-22
@@ -9,6 +9,7 @@
9
10
import type {ReactElement} from 'shared/ReactElementType';
11
import type {ReactPortal} from 'shared/ReactTypes';
12
+import type {BlockComponent} from 'react/src/block';
13
import type {Fiber} from './ReactFiber';
14
import type {ExpirationTime} from './ReactFiberExpirationTime';
15
@@ -47,6 +48,7 @@ import {
48
} from './ReactCurrentFiber';
49
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
50
import {StrictMode} from './ReactTypeOfMode';
51
+import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
52
53
let didWarnAboutMaps;
54
let didWarnAboutGenerators;
@@ -420,18 +422,25 @@ function ChildReconciler(shouldTrackSideEffects) {
422
} else if (
423
enableBlocksAPI &&
424
current.tag === Block &&
423
- element.type.$$typeof === REACT_BLOCK_TYPE &&
424
- element.type.render === current.type.render
425
+ element.type.$$typeof === REACT_BLOCK_TYPE
426
) {
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;
443
}
434
- return existing;
444
}
445
}
446
// Insert
@@ -1179,19 +1188,24 @@ function ChildReconciler(shouldTrackSideEffects) {
1188
}
1189
case Block:
1190
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;
1208
}
1194
- return existing;
1209
}
1210
}
1211
// We intentionally fallthrough here if enableBlocksAPI is not on.
packages/react-reconciler/src/ReactFiberBeginWork.js
+12
-4
@@ -8,6 +8,7 @@
8
*/
9
10
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
11
+import type {BlockComponent} from 'react/src/block';
12
import type {Fiber} from './ReactFiber';
13
import type {FiberRoot} from './ReactFiberRoot';
14
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -167,6 +168,7 @@ import {
168
readLazyComponentType,
169
resolveDefaultProps,
170
} from './ReactFiberLazyComponent';
171
+import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
172
import {
173
resolveLazyComponentTag,
174
createFiberFromTypeAndProps,
@@ -182,6 +184,7 @@ import {
184
renderDidSuspendDelayIfPossible,
185
markUnprocessedUpdateTime,
186
} from './ReactFiberWorkLoop';
187
+import {Resolved} from 'shared/ReactLazyStatusTags';
188
189
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
190
@@ -700,10 +703,10 @@ function updateFunctionComponent(
703
return workInProgress.child;
704
}
705
703
-function updateBlock(
706
+function updateBlock<Props, Payload, Data>(
707
current: Fiber | null,
708
workInProgress: Fiber,
706
- block: any,
709
+ block: BlockComponent<Props, Payload, Data>,
710
nextProps: any,
711
renderExpirationTime: ExpirationTime,
712
) {
@@ -711,8 +714,13 @@ function updateBlock(
714
// hasn't yet mounted. This happens after the first render suspends.
715
// We'll need to figure out if this is fine or can cause issues.
716
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;
724
725
// The rest is a fork of updateFunctionComponent
726
let nextChildren;
packages/react-reconciler/src/ReactFiberHooks.js
+4
-4
@@ -343,12 +343,12 @@ function areHookInputsEqual(
343
return true;
344
}
345
346
-export function renderWithHooks(
346
+export function renderWithHooks<Props, SecondArg>(
347
current: Fiber | null,
348
workInProgress: Fiber,
349
- Component: any,
350
- props: any,
351
- secondArg: any,
349
+ Component: (p: Props, arg: SecondArg) => any,
350
+ props: Props,
351
+ secondArg: SecondArg,
352
nextRenderExpirationTime: ExpirationTime,
353
): any {
354
renderExpirationTime = nextRenderExpirationTime;
packages/react/src/block.js
+66
-9
@@ -3,6 +3,8 @@
3
*
4
* This source code is licensed under the MIT license found in the
5
* LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
*/
9
10
import {
@@ -11,14 +13,64 @@ import {
13
REACT_FORWARD_REF_TYPE,
14
} from 'shared/ReactSymbols';
15
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
+
66
opaque type Block<Props>: React$AbstractComponent<
67
Props,
68
null,
69
> = React$AbstractComponent<Props, null>;
70
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>,
74
): (...args: Args) => Block<Props> {
75
if (__DEV__) {
76
if (typeof query !== 'function') {
@@ -63,14 +115,19 @@ export default function block<Args, Props, Data>(
115
);
116
}
117
}
118
+ function initializer(args) {
119
+ let data = query.apply(null, args);
120
+ return [data, render];
121
+ }
122
return function(): Block<Props> {
67
- let args = arguments;
68
- return {
123
+ let args: Args = arguments;
124
+ let blockComponent: UninitializedBlockComponent<Props, Args, Data> = {
125
$$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,
129
};
130
+ // $FlowFixMe
131
+ return blockComponent;
132
};
133
}
packages/shared/ReactLazyComponent.js
+54
@@ -14,6 +14,13 @@ import type {
14
LazyComponent,
15
} from 'react/src/ReactLazy';
16
17
+import type {
18
+ PendingBlockComponent,
19
+ ResolvedBlockComponent,
20
+ RejectedBlockComponent,
21
+ BlockComponent,
22
+} from 'react/src/block';
23
+
24
import {
25
Uninitialized,
26
Pending,
@@ -74,3 +81,50 @@ export function initializeLazyComponentType(
81
);
82
}
83
}
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
+}