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;