Skip to content

Commit d26721f

Browse files
committed
Switch Blocks to using a Lazy component wrapper
Then resolve to a true Block inside.
1 parent 5ba4026 commit d26721f

File tree

6 files changed

+125
-205
lines changed

6 files changed

+125
-205
lines changed

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type {ReactElement} from 'shared/ReactElementType';
1111
import type {ReactPortal} from 'shared/ReactTypes';
1212
import type {BlockComponent} from 'react/src/block';
13+
import type {LazyComponent} from 'react/src/ReactLazy';
1314
import type {Fiber} from './ReactFiber';
1415
import type {ExpirationTime} from './ReactFiberExpirationTime';
1516

@@ -20,6 +21,7 @@ import {
2021
REACT_ELEMENT_TYPE,
2122
REACT_FRAGMENT_TYPE,
2223
REACT_PORTAL_TYPE,
24+
REACT_LAZY_TYPE,
2325
REACT_BLOCK_TYPE,
2426
} from 'shared/ReactSymbols';
2527
import {
@@ -48,7 +50,6 @@ import {
4850
} from './ReactCurrentFiber';
4951
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
5052
import {StrictMode} from './ReactTypeOfMode';
51-
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
5253

5354
let didWarnAboutMaps;
5455
let didWarnAboutGenerators;
@@ -263,6 +264,22 @@ function warnOnFunctionType() {
263264
}
264265
}
265266

267+
// We avoid inlining this to avoid potential deopts from using try/catch.
268+
/** @noinline */
269+
function resolveLazyType<T, P>(
270+
lazyComponent: LazyComponent<T, P>,
271+
): LazyComponent<T, P> | T {
272+
try {
273+
// If we can, let's peak at the resulting type.
274+
let payload = lazyComponent._payload;
275+
let init = lazyComponent._init;
276+
return init(payload);
277+
} catch (x) {
278+
// Leave it in place and let it throw again in the begin phase.
279+
return lazyComponent;
280+
}
281+
}
282+
266283
// This wrapper function exists because I expect to clone the code in each path
267284
// to be able to optimize each path individually by branching early. This needs
268285
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -419,22 +436,22 @@ function ChildReconciler(shouldTrackSideEffects) {
419436
existing._debugOwner = element._owner;
420437
}
421438
return existing;
422-
} else if (
423-
enableBlocksAPI &&
424-
current.tag === Block &&
425-
element.type.$$typeof === REACT_BLOCK_TYPE
426-
) {
439+
} else if (enableBlocksAPI && current.tag === Block) {
427440
// The new Block might not be initialized yet. We need to initialize
428441
// it in case initializing it turns out it would match.
429-
initializeBlockComponentType(element.type);
442+
let type = element.type;
443+
if (type.$$typeof === REACT_LAZY_TYPE) {
444+
type = resolveLazyType(type);
445+
}
430446
if (
431-
(element.type: BlockComponent<any, any, any>)._fn ===
432-
(current.type: BlockComponent<any, any, any>)._fn
447+
type.$$typeof === REACT_BLOCK_TYPE &&
448+
((type: any): BlockComponent<any, any>)._render ===
449+
(current.type: BlockComponent<any, any>)._render
433450
) {
434451
// Same as above but also update the .type field.
435452
const existing = useFiber(current, element.props);
436453
existing.return = returnFiber;
437-
existing.type = element.type;
454+
existing.type = type;
438455
if (__DEV__) {
439456
existing._debugSource = element._source;
440457
existing._debugOwner = element._owner;
@@ -1188,17 +1205,20 @@ function ChildReconciler(shouldTrackSideEffects) {
11881205
}
11891206
case Block:
11901207
if (enableBlocksAPI) {
1191-
if (element.type.$$typeof === REACT_BLOCK_TYPE) {
1208+
let type = element.type;
1209+
if (type.$$typeof === REACT_LAZY_TYPE) {
1210+
type = resolveLazyType(type);
1211+
}
1212+
if (type.$$typeof === REACT_BLOCK_TYPE) {
11921213
// The new Block might not be initialized yet. We need to initialize
11931214
// it in case initializing it turns out it would match.
1194-
initializeBlockComponentType(element.type);
11951215
if (
1196-
(element.type: BlockComponent<any, any, any>)._fn ===
1197-
(child.type: BlockComponent<any, any, any>)._fn
1216+
((type: any): BlockComponent<any, any>)._render ===
1217+
(child.type: BlockComponent<any, any>)._render
11981218
) {
11991219
deleteRemainingChildren(returnFiber, child.sibling);
12001220
const existing = useFiber(child, element.props);
1201-
existing.type = element.type;
1221+
existing.type = type;
12021222
existing.return = returnFiber;
12031223
if (__DEV__) {
12041224
existing._debugSource = element._source;

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ import {
165165
updateClassInstance,
166166
} from './ReactFiberClassComponent';
167167
import {resolveDefaultProps} from './ReactFiberLazyComponent';
168-
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
169168
import {
170169
resolveLazyComponentTag,
171170
createFiberFromTypeAndProps,
@@ -181,7 +180,6 @@ import {
181180
renderDidSuspendDelayIfPossible,
182181
markUnprocessedUpdateTime,
183182
} from './ReactFiberWorkLoop';
184-
import {Resolved} from 'shared/ReactLazyStatusTags';
185183

186184
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
187185

@@ -707,23 +705,18 @@ function updateFunctionComponent(
707705
return workInProgress.child;
708706
}
709707

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

721-
initializeBlockComponentType(block);
722-
if (block._status !== Resolved) {
723-
throw block._data;
724-
}
725-
726-
const render = block._fn;
719+
const render = block._render;
727720
const data = block._data;
728721

729722
// The rest is a fork of updateFunctionComponent

packages/react-reconciler/src/__tests__/ReactBlocks-test.js

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ let useState;
1414
let Suspense;
1515
let block;
1616
let readString;
17+
let Scheduler;
1718

1819
describe('ReactBlocks', () => {
1920
beforeEach(() => {
2021
jest.resetModules();
2122

23+
Scheduler = require('scheduler');
2224
React = require('react');
2325
ReactNoop = require('react-noop-renderer');
2426

@@ -86,64 +88,64 @@ describe('ReactBlocks', () => {
8688
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
8789
});
8890

89-
it.experimental('supports a lazy wrapper around a chunk', async () => {
90-
function Query(id) {
91-
return {
92-
id: id,
93-
name: readString('Sebastian'),
94-
};
95-
}
91+
it.experimental(
92+
'does not support a lazy wrapper around a chunk',
93+
async () => {
94+
function Query(id) {
95+
return {
96+
id: id,
97+
name: readString('Sebastian'),
98+
};
99+
}
96100

97-
function Render(props, data) {
98-
return (
99-
<span>
100-
{props.title}: {data.name}
101-
</span>
102-
);
103-
}
101+
function Render(props, data) {
102+
return (
103+
<span>
104+
{props.title}: {data.name}
105+
</span>
106+
);
107+
}
104108

105-
let loadUser = block(Query, Render);
109+
let loadUser = block(Query, Render);
106110

107-
function App({User}) {
108-
return (
109-
<Suspense fallback={'Loading...'}>
110-
<User title="Name" />
111-
</Suspense>
111+
function App({User}) {
112+
return (
113+
<Suspense fallback={'Loading...'}>
114+
<User title="Name" />
115+
</Suspense>
116+
);
117+
}
118+
119+
let resolveLazy;
120+
let LazyUser = React.lazy(
121+
() =>
122+
new Promise(resolve => {
123+
resolveLazy = function() {
124+
resolve({
125+
default: loadUser(123),
126+
});
127+
};
128+
}),
112129
);
113-
}
114130

115-
let resolveLazy;
116-
let LazyUser = React.lazy(
117-
() =>
118-
new Promise(resolve => {
119-
resolveLazy = function() {
120-
resolve({
121-
default: loadUser(123),
122-
});
123-
};
124-
}),
125-
);
131+
await ReactNoop.act(async () => {
132+
ReactNoop.render(<App User={LazyUser} />);
133+
});
126134

127-
await ReactNoop.act(async () => {
128-
ReactNoop.render(<App User={LazyUser} />);
129-
});
135+
expect(ReactNoop).toMatchRenderedOutput('Loading...');
130136

131-
expect(ReactNoop).toMatchRenderedOutput('Loading...');
132-
133-
// Resolve the component.
134-
await ReactNoop.act(async () => {
137+
// Resolve the component.
135138
await resolveLazy();
136-
});
137-
138-
// We're still waiting on the data.
139-
expect(ReactNoop).toMatchRenderedOutput('Loading...');
140-
141-
await ReactNoop.act(async () => {
142-
jest.advanceTimersByTime(1000);
143-
});
144139

145-
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
146-
});
140+
expect(Scheduler).toFlushAndThrow(
141+
'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
142+
'Lazy element type must resolve to a class or function.' +
143+
(__DEV__
144+
? ' Did you wrap a component in React.lazy() more than once?'
145+
: ''),
146+
);
147+
},
148+
);
147149

148150
it.experimental(
149151
'can receive updated data for the same component',

packages/react/src/block.js

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
* @flow
88
*/
99

10+
import type {LazyComponent} from './ReactLazy';
11+
1012
import {
13+
REACT_LAZY_TYPE,
1114
REACT_BLOCK_TYPE,
1215
REACT_MEMO_TYPE,
1316
REACT_FORWARD_REF_TYPE,
@@ -19,55 +22,33 @@ type BlockRenderFunction<Props, Data> = (
1922
data: Data,
2023
) => React$Node;
2124

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,
25+
type Payload<Props, Args: Iterable<any>, Data> = {
26+
query: BlockQueryFunction<Args, Data>,
27+
args: Args,
28+
render: BlockRenderFunction<Props, Data>,
4429
};
4530

46-
export type ResolvedBlockComponent<Props, Data> = {
31+
export type BlockComponent<Props, Data> = {
4732
$$typeof: Symbol | number,
48-
_status: 1,
4933
_data: Data,
50-
_fn: BlockRenderFunction<Props, Data>,
34+
_render: BlockRenderFunction<Props, Data>,
5135
};
5236

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-
6637
opaque type Block<Props>: React$AbstractComponent<
6738
Props,
6839
null,
6940
> = React$AbstractComponent<Props, null>;
7041

42+
function lazyInitializer<Props, Args: Iterable<any>, Data>(
43+
payload: Payload<Props, Args, Data>,
44+
): BlockComponent<Props, Data> {
45+
return {
46+
$$typeof: REACT_BLOCK_TYPE,
47+
_data: payload.query.apply(null, payload.args),
48+
_render: payload.render,
49+
};
50+
}
51+
7152
export default function block<Args: Iterable<any>, Props, Data>(
7253
query: BlockQueryFunction<Args, Data>,
7354
render: BlockRenderFunction<Props, Data>,
@@ -115,19 +96,26 @@ export default function block<Args: Iterable<any>, Props, Data>(
11596
);
11697
}
11798
}
118-
function initializer(args) {
119-
let data = query.apply(null, args);
120-
return [data, render];
121-
}
99+
122100
return function(): Block<Props> {
123101
let args: Args = arguments;
124-
let blockComponent: UninitializedBlockComponent<Props, Args, Data> = {
125-
$$typeof: REACT_BLOCK_TYPE,
126-
_status: -1,
127-
_data: args,
128-
_fn: initializer,
102+
103+
let payload: Payload<Props, Args, Data> = {
104+
query: query,
105+
args: args,
106+
render: render,
129107
};
108+
109+
let lazyType: LazyComponent<
110+
BlockComponent<Props, Data>,
111+
Payload<Props, Args, Data>,
112+
> = {
113+
$$typeof: REACT_LAZY_TYPE,
114+
_payload: payload,
115+
_init: lazyInitializer,
116+
};
117+
130118
// $FlowFixMe
131-
return blockComponent;
119+
return lazyType;
132120
};
133121
}

0 commit comments

Comments
 (0)