@samitouri / QOS-React-2 / commits / 238b57f0f7

[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 +}