diff --git a/demos/suspense-lazy/Cpn.tsx b/demos/suspense-lazy/Cpn.tsx
new file mode 100644
index 0000000..7e73c30
--- /dev/null
+++ b/demos/suspense-lazy/Cpn.tsx
@@ -0,0 +1,3 @@
+export default function Cpn() {
+ return
Cpn
;
+}
diff --git a/demos/suspense-lazy/index.html b/demos/suspense-lazy/index.html
new file mode 100644
index 0000000..35449a3
--- /dev/null
+++ b/demos/suspense-lazy/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Suspense
+
+
+
+
+
+
+
diff --git a/demos/suspense-lazy/main.tsx b/demos/suspense-lazy/main.tsx
new file mode 100644
index 0000000..0a3e02a
--- /dev/null
+++ b/demos/suspense-lazy/main.tsx
@@ -0,0 +1,22 @@
+import { Suspense, lazy } from 'react';
+import ReactDOM from 'react-dom/client';
+
+function delay(promise) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(promise);
+ }, 2000);
+ });
+}
+
+const Cpn = lazy(() => import('./Cpn').then((res) => delay(res)));
+
+function App() {
+ return (
+ loading}>
+
+
+ );
+}
+
+ReactDOM.createRoot(document.getElementById('root')).render();
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index 11a21ce..216413c 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -18,7 +18,8 @@ import {
HostRoot,
HostText,
OffscreenComponent,
- SuspenseComponent
+ SuspenseComponent,
+ LazyComponent
} from './workTags';
import {
Ref,
@@ -50,6 +51,8 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
return updateSuspenseComponent(wip);
case OffscreenComponent:
return updateOffscreenComponent(wip);
+ case LazyComponent:
+ return mountLazyComponent(wip, renderLane);
default:
if (__DEV__) {
console.warn('beginWork未实现的类型');
@@ -59,6 +62,17 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
return null;
};
+function mountLazyComponent(wip: FiberNode, renderLane: Lane) {
+ const LazyType = wip.type;
+ const payload = LazyType._payload;
+ const init = LazyType._init;
+ const Component = init(payload);
+ wip.type = Component;
+ wip.tag = FunctionComponent;
+ const child = updateFunctionComponent(wip, renderLane);
+ return child;
+}
+
function updateContextProvider(wip: FiberNode) {
const providerType = wip.type;
const context = providerType._context;
diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts
index 2a4ccee..c580a59 100644
--- a/packages/react-reconciler/src/fiber.ts
+++ b/packages/react-reconciler/src/fiber.ts
@@ -6,14 +6,19 @@ import {
HostComponent,
WorkTag,
SuspenseComponent,
- OffscreenComponent
+ OffscreenComponent,
+ LazyComponent
} from './workTags';
import { Flags, NoFlags } from './fiberFlags';
import { Container } from 'hostConfig';
import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes';
import { Effect } from './fiberHooks';
import { CallbackNode } from 'scheduler';
-import { REACT_PROVIDER_TYPE, REACT_SUSPENSE_TYPE } from 'shared/ReactSymbols';
+import {
+ REACT_PROVIDER_TYPE,
+ REACT_SUSPENSE_TYPE,
+ REACT_LAZY_TYPE
+} from 'shared/ReactSymbols';
export class FiberNode {
type: any;
@@ -152,6 +157,8 @@ export function createFiberFromElement(element: ReactElementType): FiberNode {
fiberTag = ContextProvider;
} else if (type === REACT_SUSPENSE_TYPE) {
fiberTag = SuspenseComponent;
+ } else if (typeof type === 'object' && type.$$typeof === REACT_LAZY_TYPE) {
+ fiberTag = LazyComponent;
} else if (typeof type !== 'function' && __DEV__) {
console.warn('为定义的type类型', element);
}
diff --git a/packages/react-reconciler/src/workLoop.ts b/packages/react-reconciler/src/workLoop.ts
index 00329cc..3447027 100644
--- a/packages/react-reconciler/src/workLoop.ts
+++ b/packages/react-reconciler/src/workLoop.ts
@@ -61,9 +61,16 @@ const RootDidNotComplete = 3;
let workInProgressRootExitStatus: number = RootInProgress;
// Suspense
-type SuspendedReason = typeof NotSuspended | typeof SuspendedOnData;
+type SuspendedReason =
+ | typeof NotSuspended
+ | typeof SuspendedOnError
+ | typeof SuspendedOnData
+ | typeof SuspendedOnDeprecatedThrowPromise;
const NotSuspended = 0;
-const SuspendedOnData = 6;
+const SuspendedOnError = 1;
+const SuspendedOnData = 2;
+const SuspendedOnDeprecatedThrowPromise = 4;
+
let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
let workInProgressThrownValue: any = null;
@@ -416,7 +423,15 @@ function handleThrow(root: FiberRootNode, thrownValue: any): void {
workInProgressSuspendedReason = SuspendedOnData;
thrownValue = getSuspenseThenable();
} else {
- // TODO Error Boundary
+ const isWakeable =
+ thrownValue !== null &&
+ typeof thrownValue === 'object' &&
+ typeof thrownValue.then === 'function';
+
+ workInProgressThrownValue = thrownValue;
+ workInProgressSuspendedReason = isWakeable
+ ? SuspendedOnDeprecatedThrowPromise
+ : SuspendedOnError;
}
workInProgressThrownValue = thrownValue;
}
diff --git a/packages/react-reconciler/src/workTags.ts b/packages/react-reconciler/src/workTags.ts
index 0b42462..4b9bf06 100644
--- a/packages/react-reconciler/src/workTags.ts
+++ b/packages/react-reconciler/src/workTags.ts
@@ -6,7 +6,8 @@ export type WorkTag =
| typeof Fragment
| typeof ContextProvider
| typeof SuspenseComponent
- | typeof OffscreenComponent;
+ | typeof OffscreenComponent
+ | typeof LazyComponent;
export const FunctionComponent = 0;
export const HostRoot = 3;
@@ -19,3 +20,5 @@ export const ContextProvider = 8;
export const SuspenseComponent = 13;
export const OffscreenComponent = 14;
+
+export const LazyComponent = 16;
diff --git a/packages/react/index.ts b/packages/react/index.ts
index 8916935..520b904 100644
--- a/packages/react/index.ts
+++ b/packages/react/index.ts
@@ -7,6 +7,7 @@ import {
} from './src/jsx';
export { REACT_FRAGMENT_TYPE as Fragment } from 'shared/ReactSymbols';
export { createContext } from './src/context';
+export { lazy } from './src/lazy';
export { REACT_SUSPENSE_TYPE as Suspense } from 'shared/ReactSymbols';
// React
diff --git a/packages/react/src/lazy.ts b/packages/react/src/lazy.ts
new file mode 100644
index 0000000..7ff33da
--- /dev/null
+++ b/packages/react/src/lazy.ts
@@ -0,0 +1,89 @@
+import { Thenable, Wakeable } from 'shared/ReactTypes';
+import { REACT_LAZY_TYPE } from 'shared/ReactSymbols';
+
+const Uninitialized = -1;
+const Pending = 0;
+const Resolved = 1;
+const Rejected = 2;
+
+type UninitializedPayload = {
+ _status: typeof Uninitialized;
+ _result: () => Thenable<{ default: T }>;
+};
+
+type PendingPayload = {
+ _status: typeof Pending;
+ _result: Wakeable;
+};
+
+type ResolvedPayload = {
+ _status: typeof Resolved;
+ _result: { default: T };
+};
+
+type RejectedPayload = {
+ _status: typeof Rejected;
+ _result: any;
+};
+
+type Payload =
+ | UninitializedPayload
+ | PendingPayload
+ | ResolvedPayload
+ | RejectedPayload;
+
+export type LazyComponent = {
+ $$typeof: symbol | number;
+ _payload: P;
+ _init: (payload: P) => T;
+};
+
+function lazyInitializer(payload: Payload): T {
+ if (payload._status === Uninitialized) {
+ const ctor = payload._result;
+ const thenable = ctor();
+ thenable.then(
+ (moduleObject) => {
+ // @ts-ignore
+ const resolved: ResolvedPayload = payload;
+ resolved._status = Resolved;
+ resolved._result = moduleObject;
+ },
+ (error) => {
+ // @ts-ignore
+ const rejected: RejectedPayload = payload;
+ rejected._status = Rejected;
+ rejected._result = error;
+ }
+ );
+ if (payload._status === Uninitialized) {
+ // @ts-ignore
+ const pending: PendingPayload = payload;
+ pending._status = Pending;
+ pending._result = thenable;
+ }
+ }
+ if (payload._status === Resolved) {
+ const moduleObject = payload._result;
+ return moduleObject.default;
+ } else {
+ throw payload._result;
+ }
+}
+
+export function lazy(
+ ctor: () => Thenable<{ default: T }>
+): LazyComponent> {
+ const payload: Payload = {
+ _status: Uninitialized,
+ _result: ctor
+ };
+
+ const lazyType: LazyComponent> = {
+ $$typeof: REACT_LAZY_TYPE,
+ _payload: payload,
+ _init: lazyInitializer
+ };
+
+ return lazyType;
+}
diff --git a/packages/shared/ReactSymbols.ts b/packages/shared/ReactSymbols.ts
index c469bbc..c92b2a7 100644
--- a/packages/shared/ReactSymbols.ts
+++ b/packages/shared/ReactSymbols.ts
@@ -19,3 +19,7 @@ export const REACT_PROVIDER_TYPE = supportSymbol
export const REACT_SUSPENSE_TYPE = supportSymbol
? Symbol.for('react.suspense')
: 0xead1;
+
+export const REACT_LAZY_TYPE = supportSymbol
+ ? Symbol.for('react.lazy')
+ : 0xead4;