From 8542452932ba3d26c3a23e18f750a7a50a2496f6 Mon Sep 17 00:00:00 2001 From: qun <778157949@qq.com> Date: Sun, 6 Aug 2023 15:55:46 +0800 Subject: [PATCH 1/3] feat: react lazy --- demos/suspense-lazy/Cpn.tsx | 3 + demos/suspense-lazy/index.html | 14 ++++ demos/suspense-lazy/main.tsx | 22 ++++++ packages/react-reconciler/src/beginWork.ts | 17 ++++- packages/react-reconciler/src/fiber.ts | 12 ++- packages/react-reconciler/src/workLoop.ts | 21 ++++- packages/react-reconciler/src/workTags.ts | 7 +- packages/react/index.ts | 1 + packages/react/src/lazy.ts | 89 ++++++++++++++++++++++ packages/shared/ReactSymbols.ts | 4 + 10 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 demos/suspense-lazy/Cpn.tsx create mode 100644 demos/suspense-lazy/index.html create mode 100644 demos/suspense-lazy/main.tsx create mode 100644 packages/react/src/lazy.ts 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..0860faf 100644 --- a/packages/react-reconciler/src/beginWork.ts +++ b/packages/react-reconciler/src/beginWork.ts @@ -18,7 +18,9 @@ import { HostRoot, HostText, OffscreenComponent, - SuspenseComponent + SuspenseComponent, + LazyComponent, + IndeterminateComponent } from './workTags'; import { Ref, @@ -50,6 +52,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 +63,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..4e24845 100644 --- a/packages/react-reconciler/src/fiber.ts +++ b/packages/react-reconciler/src/fiber.ts @@ -6,14 +6,20 @@ import { HostComponent, WorkTag, SuspenseComponent, - OffscreenComponent + OffscreenComponent, + LazyComponent, + IndeterminateComponent } 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 +158,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..269e85f 100644 --- a/packages/react-reconciler/src/workTags.ts +++ b/packages/react-reconciler/src/workTags.ts @@ -1,14 +1,17 @@ export type WorkTag = | typeof FunctionComponent + | typeof IndeterminateComponent | typeof HostRoot | typeof HostComponent | typeof HostText | typeof Fragment | typeof ContextProvider | typeof SuspenseComponent - | typeof OffscreenComponent; + | typeof OffscreenComponent + | typeof LazyComponent; export const FunctionComponent = 0; +export const IndeterminateComponent = 2; export const HostRoot = 3; export const HostComponent = 5; @@ -19,3 +22,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; From 0f68b83eec77effd0cce9352fdee1655cc8a6f95 Mon Sep 17 00:00:00 2001 From: qun <778157949@qq.com> Date: Tue, 8 Aug 2023 23:08:01 +0800 Subject: [PATCH 2/3] feat: react lazy --- packages/react-reconciler/src/beginWork.ts | 3 +-- packages/react-reconciler/src/fiber.ts | 4 ++-- packages/react-reconciler/src/workTags.ts | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts index 0860faf..216413c 100644 --- a/packages/react-reconciler/src/beginWork.ts +++ b/packages/react-reconciler/src/beginWork.ts @@ -19,8 +19,7 @@ import { HostText, OffscreenComponent, SuspenseComponent, - LazyComponent, - IndeterminateComponent + LazyComponent } from './workTags'; import { Ref, diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts index 4e24845..83c16e9 100644 --- a/packages/react-reconciler/src/fiber.ts +++ b/packages/react-reconciler/src/fiber.ts @@ -7,8 +7,7 @@ import { WorkTag, SuspenseComponent, OffscreenComponent, - LazyComponent, - IndeterminateComponent + LazyComponent } from './workTags'; import { Flags, NoFlags } from './fiberFlags'; import { Container } from 'hostConfig'; @@ -159,6 +158,7 @@ export function createFiberFromElement(element: ReactElementType): FiberNode { } else if (type === REACT_SUSPENSE_TYPE) { fiberTag = SuspenseComponent; } else if (typeof type === 'object' && type.$$typeof === REACT_LAZY_TYPE) { + console.log('element', element); fiberTag = LazyComponent; } else if (typeof type !== 'function' && __DEV__) { console.warn('为定义的type类型', element); diff --git a/packages/react-reconciler/src/workTags.ts b/packages/react-reconciler/src/workTags.ts index 269e85f..4b9bf06 100644 --- a/packages/react-reconciler/src/workTags.ts +++ b/packages/react-reconciler/src/workTags.ts @@ -1,6 +1,5 @@ export type WorkTag = | typeof FunctionComponent - | typeof IndeterminateComponent | typeof HostRoot | typeof HostComponent | typeof HostText @@ -11,7 +10,6 @@ export type WorkTag = | typeof LazyComponent; export const FunctionComponent = 0; -export const IndeterminateComponent = 2; export const HostRoot = 3; export const HostComponent = 5; From fe29178ac72c35f4ccb89ba934672f1c7790bcba Mon Sep 17 00:00:00 2001 From: qun <68707557+L-Qun@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:53:29 +0800 Subject: [PATCH 3/3] Update fiber.ts --- packages/react-reconciler/src/fiber.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts index 83c16e9..c580a59 100644 --- a/packages/react-reconciler/src/fiber.ts +++ b/packages/react-reconciler/src/fiber.ts @@ -158,7 +158,6 @@ export function createFiberFromElement(element: ReactElementType): FiberNode { } else if (type === REACT_SUSPENSE_TYPE) { fiberTag = SuspenseComponent; } else if (typeof type === 'object' && type.$$typeof === REACT_LAZY_TYPE) { - console.log('element', element); fiberTag = LazyComponent; } else if (typeof type !== 'function' && __DEV__) { console.warn('为定义的type类型', element);