Skip to content

Commit 34cd287

Browse files
authored
Merge pull request #49 from L-Qun/feat/react-lazy
feat: react lazy
2 parents 84881b0 + fe29178 commit 34cd287

File tree

10 files changed

+179
-7
lines changed

10 files changed

+179
-7
lines changed

demos/suspense-lazy/Cpn.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Cpn() {
2+
return <div>Cpn</div>;
3+
}

demos/suspense-lazy/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Suspense</title>
8+
</head>
9+
10+
<body>
11+
<div id="root"></div>
12+
<script type="module" src="main.tsx"></script>
13+
</body>
14+
</html>

demos/suspense-lazy/main.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Suspense, lazy } from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
4+
function delay(promise) {
5+
return new Promise((resolve) => {
6+
setTimeout(() => {
7+
resolve(promise);
8+
}, 2000);
9+
});
10+
}
11+
12+
const Cpn = lazy(() => import('./Cpn').then((res) => delay(res)));
13+
14+
function App() {
15+
return (
16+
<Suspense fallback={<div>loading</div>}>
17+
<Cpn />
18+
</Suspense>
19+
);
20+
}
21+
22+
ReactDOM.createRoot(document.getElementById('root')).render(<App />);

packages/react-reconciler/src/beginWork.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
HostRoot,
1919
HostText,
2020
OffscreenComponent,
21-
SuspenseComponent
21+
SuspenseComponent,
22+
LazyComponent
2223
} from './workTags';
2324
import {
2425
Ref,
@@ -50,6 +51,8 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
5051
return updateSuspenseComponent(wip);
5152
case OffscreenComponent:
5253
return updateOffscreenComponent(wip);
54+
case LazyComponent:
55+
return mountLazyComponent(wip, renderLane);
5356
default:
5457
if (__DEV__) {
5558
console.warn('beginWork未实现的类型');
@@ -59,6 +62,17 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
5962
return null;
6063
};
6164

65+
function mountLazyComponent(wip: FiberNode, renderLane: Lane) {
66+
const LazyType = wip.type;
67+
const payload = LazyType._payload;
68+
const init = LazyType._init;
69+
const Component = init(payload);
70+
wip.type = Component;
71+
wip.tag = FunctionComponent;
72+
const child = updateFunctionComponent(wip, renderLane);
73+
return child;
74+
}
75+
6276
function updateContextProvider(wip: FiberNode) {
6377
const providerType = wip.type;
6478
const context = providerType._context;

packages/react-reconciler/src/fiber.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@ import {
66
HostComponent,
77
WorkTag,
88
SuspenseComponent,
9-
OffscreenComponent
9+
OffscreenComponent,
10+
LazyComponent
1011
} from './workTags';
1112
import { Flags, NoFlags } from './fiberFlags';
1213
import { Container } from 'hostConfig';
1314
import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes';
1415
import { Effect } from './fiberHooks';
1516
import { CallbackNode } from 'scheduler';
16-
import { REACT_PROVIDER_TYPE, REACT_SUSPENSE_TYPE } from 'shared/ReactSymbols';
17+
import {
18+
REACT_PROVIDER_TYPE,
19+
REACT_SUSPENSE_TYPE,
20+
REACT_LAZY_TYPE
21+
} from 'shared/ReactSymbols';
1722

1823
export class FiberNode {
1924
type: any;
@@ -152,6 +157,8 @@ export function createFiberFromElement(element: ReactElementType): FiberNode {
152157
fiberTag = ContextProvider;
153158
} else if (type === REACT_SUSPENSE_TYPE) {
154159
fiberTag = SuspenseComponent;
160+
} else if (typeof type === 'object' && type.$$typeof === REACT_LAZY_TYPE) {
161+
fiberTag = LazyComponent;
155162
} else if (typeof type !== 'function' && __DEV__) {
156163
console.warn('为定义的type类型', element);
157164
}

packages/react-reconciler/src/workLoop.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,16 @@ const RootDidNotComplete = 3;
6161
let workInProgressRootExitStatus: number = RootInProgress;
6262

6363
// Suspense
64-
type SuspendedReason = typeof NotSuspended | typeof SuspendedOnData;
64+
type SuspendedReason =
65+
| typeof NotSuspended
66+
| typeof SuspendedOnError
67+
| typeof SuspendedOnData
68+
| typeof SuspendedOnDeprecatedThrowPromise;
6569
const NotSuspended = 0;
66-
const SuspendedOnData = 6;
70+
const SuspendedOnError = 1;
71+
const SuspendedOnData = 2;
72+
const SuspendedOnDeprecatedThrowPromise = 4;
73+
6774
let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
6875
let workInProgressThrownValue: any = null;
6976

@@ -416,7 +423,15 @@ function handleThrow(root: FiberRootNode, thrownValue: any): void {
416423
workInProgressSuspendedReason = SuspendedOnData;
417424
thrownValue = getSuspenseThenable();
418425
} else {
419-
// TODO Error Boundary
426+
const isWakeable =
427+
thrownValue !== null &&
428+
typeof thrownValue === 'object' &&
429+
typeof thrownValue.then === 'function';
430+
431+
workInProgressThrownValue = thrownValue;
432+
workInProgressSuspendedReason = isWakeable
433+
? SuspendedOnDeprecatedThrowPromise
434+
: SuspendedOnError;
420435
}
421436
workInProgressThrownValue = thrownValue;
422437
}

packages/react-reconciler/src/workTags.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export type WorkTag =
66
| typeof Fragment
77
| typeof ContextProvider
88
| typeof SuspenseComponent
9-
| typeof OffscreenComponent;
9+
| typeof OffscreenComponent
10+
| typeof LazyComponent;
1011

1112
export const FunctionComponent = 0;
1213
export const HostRoot = 3;
@@ -19,3 +20,5 @@ export const ContextProvider = 8;
1920

2021
export const SuspenseComponent = 13;
2122
export const OffscreenComponent = 14;
23+
24+
export const LazyComponent = 16;

packages/react/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from './src/jsx';
88
export { REACT_FRAGMENT_TYPE as Fragment } from 'shared/ReactSymbols';
99
export { createContext } from './src/context';
10+
export { lazy } from './src/lazy';
1011
export { REACT_SUSPENSE_TYPE as Suspense } from 'shared/ReactSymbols';
1112
// React
1213

packages/react/src/lazy.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { Thenable, Wakeable } from 'shared/ReactTypes';
2+
import { REACT_LAZY_TYPE } from 'shared/ReactSymbols';
3+
4+
const Uninitialized = -1;
5+
const Pending = 0;
6+
const Resolved = 1;
7+
const Rejected = 2;
8+
9+
type UninitializedPayload<T> = {
10+
_status: typeof Uninitialized;
11+
_result: () => Thenable<{ default: T }>;
12+
};
13+
14+
type PendingPayload = {
15+
_status: typeof Pending;
16+
_result: Wakeable;
17+
};
18+
19+
type ResolvedPayload<T> = {
20+
_status: typeof Resolved;
21+
_result: { default: T };
22+
};
23+
24+
type RejectedPayload = {
25+
_status: typeof Rejected;
26+
_result: any;
27+
};
28+
29+
type Payload<T> =
30+
| UninitializedPayload<T>
31+
| PendingPayload
32+
| ResolvedPayload<T>
33+
| RejectedPayload;
34+
35+
export type LazyComponent<T, P> = {
36+
$$typeof: symbol | number;
37+
_payload: P;
38+
_init: (payload: P) => T;
39+
};
40+
41+
function lazyInitializer<T>(payload: Payload<T>): T {
42+
if (payload._status === Uninitialized) {
43+
const ctor = payload._result;
44+
const thenable = ctor();
45+
thenable.then(
46+
(moduleObject) => {
47+
// @ts-ignore
48+
const resolved: ResolvedPayload<T> = payload;
49+
resolved._status = Resolved;
50+
resolved._result = moduleObject;
51+
},
52+
(error) => {
53+
// @ts-ignore
54+
const rejected: RejectedPayload = payload;
55+
rejected._status = Rejected;
56+
rejected._result = error;
57+
}
58+
);
59+
if (payload._status === Uninitialized) {
60+
// @ts-ignore
61+
const pending: PendingPayload = payload;
62+
pending._status = Pending;
63+
pending._result = thenable;
64+
}
65+
}
66+
if (payload._status === Resolved) {
67+
const moduleObject = payload._result;
68+
return moduleObject.default;
69+
} else {
70+
throw payload._result;
71+
}
72+
}
73+
74+
export function lazy<T>(
75+
ctor: () => Thenable<{ default: T }>
76+
): LazyComponent<T, Payload<T>> {
77+
const payload: Payload<T> = {
78+
_status: Uninitialized,
79+
_result: ctor
80+
};
81+
82+
const lazyType: LazyComponent<T, Payload<T>> = {
83+
$$typeof: REACT_LAZY_TYPE,
84+
_payload: payload,
85+
_init: lazyInitializer
86+
};
87+
88+
return lazyType;
89+
}

packages/shared/ReactSymbols.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ export const REACT_PROVIDER_TYPE = supportSymbol
1919
export const REACT_SUSPENSE_TYPE = supportSymbol
2020
? Symbol.for('react.suspense')
2121
: 0xead1;
22+
23+
export const REACT_LAZY_TYPE = supportSymbol
24+
? Symbol.for('react.lazy')
25+
: 0xead4;

0 commit comments

Comments
 (0)