Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: react lazy #49

Merged
merged 3 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions demos/suspense-lazy/Cpn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Cpn() {
return <div>Cpn</div>;
}
14 changes: 14 additions & 0 deletions demos/suspense-lazy/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Suspense</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="main.tsx"></script>
</body>
</html>
22 changes: 22 additions & 0 deletions demos/suspense-lazy/main.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Suspense fallback={<div>loading</div>}>
<Cpn />
</Suspense>
);
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
16 changes: 15 additions & 1 deletion packages/react-reconciler/src/beginWork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
HostRoot,
HostText,
OffscreenComponent,
SuspenseComponent
SuspenseComponent,
LazyComponent
} from './workTags';
import {
Ref,
Expand Down Expand Up @@ -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未实现的类型');
Expand All @@ -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;
Expand Down
11 changes: 9 additions & 2 deletions packages/react-reconciler/src/fiber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
21 changes: 18 additions & 3 deletions packages/react-reconciler/src/workLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
L-Qun marked this conversation as resolved.
Show resolved Hide resolved
: SuspendedOnError;
}
workInProgressThrownValue = thrownValue;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/react-reconciler/src/workTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,3 +20,5 @@ export const ContextProvider = 8;

export const SuspenseComponent = 13;
export const OffscreenComponent = 14;

export const LazyComponent = 16;
1 change: 1 addition & 0 deletions packages/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
89 changes: 89 additions & 0 deletions packages/react/src/lazy.ts
Original file line number Diff line number Diff line change
@@ -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<T> = {
_status: typeof Uninitialized;
_result: () => Thenable<{ default: T }>;
};

type PendingPayload = {
_status: typeof Pending;
_result: Wakeable;
};

type ResolvedPayload<T> = {
_status: typeof Resolved;
_result: { default: T };
};

type RejectedPayload = {
_status: typeof Rejected;
_result: any;
};

type Payload<T> =
| UninitializedPayload<T>
| PendingPayload
| ResolvedPayload<T>
| RejectedPayload;

export type LazyComponent<T, P> = {
$$typeof: symbol | number;
_payload: P;
_init: (payload: P) => T;
};

function lazyInitializer<T>(payload: Payload<T>): T {
if (payload._status === Uninitialized) {
const ctor = payload._result;
const thenable = ctor();
thenable.then(
(moduleObject) => {
// @ts-ignore
const resolved: ResolvedPayload<T> = 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<T>(
ctor: () => Thenable<{ default: T }>
): LazyComponent<T, Payload<T>> {
const payload: Payload<T> = {
_status: Uninitialized,
_result: ctor
};

const lazyType: LazyComponent<T, Payload<T>> = {
$$typeof: REACT_LAZY_TYPE,
_payload: payload,
_init: lazyInitializer
};

return lazyType;
}
4 changes: 4 additions & 0 deletions packages/shared/ReactSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;