;
const pending = updateQueue.shared.pending;
updateQueue.shared.pending = null;
const { memoizedState } = processUpdateQueue(baseState, pending, renderLanes);
From 41e1b4aff567804a872a19674c1a6efcce07abf6 Mon Sep 17 00:00:00 2001
From: kasong <313439271@qq.com>
Date: Thu, 20 Apr 2023 22:52:52 +0800
Subject: [PATCH 11/19] feat: ref
---
demos/ref/index.html | 16 +++
demos/ref/main.tsx | 25 +++++
demos/ref/vite-env.d.ts | 1 +
demos/vite.config.js | 4 +-
package.json | 2 +-
packages/react-reconciler/src/beginWork.ts | 13 +++
packages/react-reconciler/src/commitWork.ts | 104 +++++++++++++-----
packages/react-reconciler/src/completeWork.ts | 15 ++-
packages/react-reconciler/src/fiber.ts | 4 +-
packages/react-reconciler/src/fiberFlags.ts | 4 +-
packages/react-reconciler/src/fiberHooks.ts | 18 ++-
packages/react-reconciler/src/workLoop.ts | 2 +
packages/react/index.ts | 5 +
packages/react/src/currentDispatcher.ts | 1 +
packages/shared/ReactTypes.ts | 2 +-
15 files changed, 181 insertions(+), 35 deletions(-)
create mode 100644 demos/ref/index.html
create mode 100644 demos/ref/main.tsx
create mode 100644 demos/ref/vite-env.d.ts
diff --git a/demos/ref/index.html b/demos/ref/index.html
new file mode 100644
index 0000000..5d0dbe9
--- /dev/null
+++ b/demos/ref/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ noop-renderer测试
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/ref/main.tsx b/demos/ref/main.tsx
new file mode 100644
index 0000000..801c24e
--- /dev/null
+++ b/demos/ref/main.tsx
@@ -0,0 +1,25 @@
+import { useState, useEffect, useRef } from 'react';
+import { createRoot } from 'react-dom/client';
+
+function App() {
+ const [isDel, del] = useState(false);
+ const divRef = useRef(null);
+
+ console.warn('render divRef', divRef.current);
+
+ useEffect(() => {
+ console.warn('useEffect divRef', divRef.current);
+ }, []);
+
+ return (
+ del(true)}>
+ {isDel ? null : }
+
+ );
+}
+
+function Child() {
+ return console.warn('dom is:', dom)}>Child
;
+}
+
+createRoot(document.getElementById('root') as HTMLElement).render();
diff --git a/demos/ref/vite-env.d.ts b/demos/ref/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/demos/ref/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/demos/vite.config.js b/demos/vite.config.js
index 34c4583..8ceff5b 100644
--- a/demos/vite.config.js
+++ b/demos/vite.config.js
@@ -34,8 +34,8 @@ export default defineConfig({
find: 'hostConfig',
replacement: path.resolve(
__dirname,
- '../packages/react-noop-renderer/src/hostConfig.ts'
- // '../packages/react-dom/src/hostConfig.ts'
+ // '../packages/react-noop-renderer/src/hostConfig.ts'
+ '../packages/react-dom/src/hostConfig.ts'
)
}
]
diff --git a/package.json b/package.json
index 937b779..c2c2a1b 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"license": "MIT",
"scripts": {
"build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
- "demo": "vite serve demos/noop-renderer --config demos/vite.config.js --force",
+ "demo": "vite serve demos/ref --config demos/vite.config.js --force",
"lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages",
"test": "jest --config scripts/jest/jest.config.js"
},
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index 9dc66a8..e8a67d8 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -11,6 +11,7 @@ import {
HostRoot,
HostText
} from './workTags';
+import { Ref } from './fiberFlags';
export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
if (__LOG__) {
@@ -55,6 +56,7 @@ function updateHostComponent(workInProgress: FiberNode, renderLanes: Lanes) {
// 根据element创建fiberNode
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
+ markRef(workInProgress.alternate, workInProgress);
reconcileChildren(workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -97,3 +99,14 @@ function reconcileChildren(
);
}
}
+
+function markRef(current: FiberNode | null, workInProgress: FiberNode) {
+ const ref = workInProgress.ref;
+
+ if (
+ (current === null && ref !== null) ||
+ (current !== null && current.ref !== ref)
+ ) {
+ workInProgress.flags |= Ref;
+ }
+}
diff --git a/packages/react-reconciler/src/commitWork.ts b/packages/react-reconciler/src/commitWork.ts
index cbdc492..d7f8cd5 100644
--- a/packages/react-reconciler/src/commitWork.ts
+++ b/packages/react-reconciler/src/commitWork.ts
@@ -2,12 +2,14 @@ import { FiberNode, FiberRootNode, PendingPassiveEffects } from './fiber';
import {
ChildDeletion,
Flags,
+ LayoutMask,
MutationMask,
NoFlags,
PassiveEffect,
PassiveMask,
Placement,
- Update
+ Update,
+ Ref
} from './fiberFlags';
import { Effect, FCUpdateQueue } from './fiberHooks';
import { HookHasEffect } from './hookEffectTags';
@@ -29,42 +31,42 @@ import {
let nextEffect: FiberNode | null = null;
// 以DFS形式执行
-export const commitMutationEffects = (
- finishedWork: FiberNode,
- root: FiberRootNode
+const commitEffects = (
+ phrase: 'mutation' | 'layout',
+ mask: Flags,
+ callback: (fiber: FiberNode, root: FiberRootNode) => void
) => {
- nextEffect = finishedWork;
+ return (finishedWork: FiberNode, root: FiberRootNode) => {
+ nextEffect = finishedWork;
- while (nextEffect !== null) {
- // 向下遍历
- const child: FiberNode | null = nextEffect.child;
+ while (nextEffect !== null) {
+ // 向下遍历
+ const child: FiberNode | null = nextEffect.child;
- if (
- (nextEffect.subtreeFlags & (MutationMask | PassiveMask)) !== NoFlags &&
- child !== null
- ) {
- nextEffect = child;
- } else {
- // 向上遍历
- up: while (nextEffect !== null) {
- commitMutationEffectsOnFiber(nextEffect, root);
- const sibling: FiberNode | null = nextEffect.sibling;
-
- if (sibling !== null) {
- nextEffect = sibling;
- break up;
+ if ((nextEffect.subtreeFlags & mask) !== NoFlags && child !== null) {
+ nextEffect = child;
+ } else {
+ // 向上遍历
+ up: while (nextEffect !== null) {
+ callback(nextEffect, root);
+ const sibling: FiberNode | null = nextEffect.sibling;
+
+ if (sibling !== null) {
+ nextEffect = sibling;
+ break up;
+ }
+ nextEffect = nextEffect.return;
}
- nextEffect = nextEffect.return;
}
}
- }
+ };
};
const commitMutationEffectsOnFiber = (
finishedWork: FiberNode,
root: FiberRootNode
) => {
- const flags = finishedWork.flags;
+ const { flags, tag } = finishedWork;
if ((flags & Placement) !== NoFlags) {
// 插入/移动
@@ -90,8 +92,59 @@ const commitMutationEffectsOnFiber = (
commitPassiveEffect(finishedWork, root, 'update');
finishedWork.flags &= ~PassiveEffect;
}
+ if ((flags & Ref) !== NoFlags && tag === HostComponent) {
+ safelyDetachRef(finishedWork);
+ }
};
+function safelyDetachRef(current: FiberNode) {
+ const ref = current.ref;
+ if (ref !== null) {
+ if (typeof ref === 'function') {
+ ref(null);
+ } else {
+ ref.current = null;
+ }
+ }
+}
+
+const commitLayoutEffectsOnFiber = (
+ finishedWork: FiberNode,
+ root: FiberRootNode
+) => {
+ const { flags, tag } = finishedWork;
+
+ if ((flags & Ref) !== NoFlags && tag === HostComponent) {
+ // 绑定新的ref
+ safelyAttachRef(finishedWork);
+ finishedWork.flags &= ~Ref;
+ }
+};
+
+function safelyAttachRef(fiber: FiberNode) {
+ const ref = fiber.ref;
+ if (ref !== null) {
+ const instance = fiber.stateNode;
+ if (typeof ref === 'function') {
+ ref(instance);
+ } else {
+ ref.current = instance;
+ }
+ }
+}
+
+export const commitMutationEffects = commitEffects(
+ 'mutation',
+ MutationMask | PassiveMask,
+ commitMutationEffectsOnFiber
+);
+
+export const commitLayoutEffects = commitEffects(
+ 'layout',
+ LayoutMask,
+ commitLayoutEffectsOnFiber
+);
+
/**
* 难点在于目标fiber的hostSibling可能并不是他的同级sibling
* 比如: 其中:function B() {return } 所以A的hostSibling实际是B的child
@@ -249,6 +302,7 @@ function commitDeletion(childToDelete: FiberNode, root: FiberRootNode) {
case HostComponent:
recordHostChildrenToDelete(hostChildrenToDelete, unmountFiber);
// 解绑ref
+ safelyDetachRef(unmountFiber);
return;
case HostText:
recordHostChildrenToDelete(hostChildrenToDelete, unmountFiber);
diff --git a/packages/react-reconciler/src/completeWork.ts b/packages/react-reconciler/src/completeWork.ts
index 499e748..f8dc673 100644
--- a/packages/react-reconciler/src/completeWork.ts
+++ b/packages/react-reconciler/src/completeWork.ts
@@ -1,6 +1,6 @@
import { updateFiberProps } from 'react-dom/src/SyntheticEvent';
import { FiberNode } from './fiber';
-import { NoFlags, Update } from './fiberFlags';
+import { NoFlags, Ref, Update } from './fiberFlags';
import {
appendInitialChild,
createInstance,
@@ -15,6 +15,10 @@ import {
HostText
} from './workTags';
+function markRef(fiber: FiberNode) {
+ fiber.flags |= Ref;
+}
+
const appendAllChildren = (parent: Instance, workInProgress: FiberNode) => {
// 遍历workInProgress所有子孙 DOM元素,依次挂载
let node = workInProgress.child;
@@ -74,13 +78,20 @@ export const completeWork = (workInProgress: FiberNode) => {
// 不应该在此处调用updateFiberProps,应该跟着判断属性变化的逻辑,在这里打flag
// 再在commitWork中更新fiberProps,我准备把这个过程留到「属性变化」相关需求一起做
updateFiberProps(workInProgress.stateNode, newProps);
+ // 标记Ref
+ if (current.ref !== workInProgress.ref) {
+ markRef(workInProgress);
+ }
} else {
// 初始化DOM
const instance = createInstance(workInProgress.type, newProps);
// 挂载DOM
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
-
+ // 标记Ref
+ if (workInProgress.ref !== null) {
+ markRef(workInProgress);
+ }
// TODO 初始化元素属性
}
// 冒泡flag
diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts
index 3f419a5..23b35d3 100644
--- a/packages/react-reconciler/src/fiber.ts
+++ b/packages/react-reconciler/src/fiber.ts
@@ -112,7 +112,7 @@ export function createFiberFromElement(
element: ReactElement,
lanes: Lanes
): FiberNode {
- const { type, key, props } = element;
+ const { type, key, props, ref } = element;
let fiberTag: WorkTag = FunctionComponent;
if (typeof type === 'string') {
@@ -123,6 +123,7 @@ export function createFiberFromElement(
const fiber = new FiberNode(fiberTag, props, key);
fiber.type = type;
fiber.lanes = lanes;
+ fiber.ref = ref;
return fiber;
}
@@ -166,6 +167,7 @@ export const createWorkInProgress = (
// 数据
wip.memoizedProps = current.memoizedProps;
wip.memoizedState = current.memoizedState;
+ wip.ref = current.ref;
wip.lanes = current.lanes;
diff --git a/packages/react-reconciler/src/fiberFlags.ts b/packages/react-reconciler/src/fiberFlags.ts
index a89941e..7ac59c3 100644
--- a/packages/react-reconciler/src/fiberFlags.ts
+++ b/packages/react-reconciler/src/fiberFlags.ts
@@ -7,8 +7,10 @@ export const ChildDeletion = 0b00000000000000000000010000;
// useEffect
export const PassiveEffect = 0b00000000000000000000100000;
+export const Ref = 0b00000000000000000001000000;
-export const MutationMask = Placement | Update | ChildDeletion;
+export const MutationMask = Placement | Update | ChildDeletion | Ref;
+export const LayoutMask = Ref;
// 删除子节点可能触发useEffect destroy
export const PassiveMask = PassiveEffect | ChildDeletion;
diff --git a/packages/react-reconciler/src/fiberHooks.ts b/packages/react-reconciler/src/fiberHooks.ts
index 49667fb..9952414 100644
--- a/packages/react-reconciler/src/fiberHooks.ts
+++ b/packages/react-reconciler/src/fiberHooks.ts
@@ -68,12 +68,14 @@ export const renderWithHooks = (workInProgress: FiberNode, lane: Lane) => {
const HooksDispatcherOnMount: Dispatcher = {
useState: mountState,
- useEffect: mountEffect
+ useEffect: mountEffect,
+ useRef: mountRef
};
const HooksDispatcherOnUpdate: Dispatcher = {
useState: updateState,
- useEffect: updateEffect
+ useEffect: updateEffect,
+ useRef: updateRef
};
function mountState(
@@ -226,6 +228,18 @@ function areHookInputsEqual(nextDeps: TEffectDeps, prevDeps: TEffectDeps) {
return true;
}
+function mountRef(initialValue: T): { current: T } {
+ const hook = mountWorkInProgressHook();
+ const ref = { current: initialValue };
+ hook.memoizedState = ref;
+ return ref;
+}
+
+function updateRef(initialValue: T): { current: T } {
+ const hook = updateWorkInProgressHook();
+ return hook.memoizedState;
+}
+
export interface Effect {
tag: Flags;
create: TEffectCallback | void;
diff --git a/packages/react-reconciler/src/workLoop.ts b/packages/react-reconciler/src/workLoop.ts
index 254de1f..ac25f10 100644
--- a/packages/react-reconciler/src/workLoop.ts
+++ b/packages/react-reconciler/src/workLoop.ts
@@ -3,6 +3,7 @@ import {
commitHookEffectListDestroy,
commitHookEffectListMount,
commitHookEffectListUnmount,
+ commitLayoutEffects,
commitMutationEffects
} from './commitWork';
import { completeWork } from './completeWork';
@@ -338,6 +339,7 @@ function commitRoot(root: FiberRootNode) {
root.current = finishedWork;
// 阶段3/3:Layout
+ commitLayoutEffects(finishedWork, root);
executionContext = prevExecutionContext;
} else {
diff --git a/packages/react/index.ts b/packages/react/index.ts
index 1964204..88e19b0 100644
--- a/packages/react/index.ts
+++ b/packages/react/index.ts
@@ -15,6 +15,11 @@ export const useEffect: Dispatcher['useEffect'] = (create, deps) => {
return dispatcher.useEffect(create, deps);
};
+export const useRef: Dispatcher['useRef'] = (initialValue) => {
+ const dispatcher = resolveDispatcher() as Dispatcher;
+ return dispatcher.useRef(initialValue);
+};
+
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
currentDispatcher
};
diff --git a/packages/react/src/currentDispatcher.ts b/packages/react/src/currentDispatcher.ts
index 802cf05..0fc2ef9 100644
--- a/packages/react/src/currentDispatcher.ts
+++ b/packages/react/src/currentDispatcher.ts
@@ -3,6 +3,7 @@ import { Action } from 'shared/ReactTypes';
export type Dispatcher = {
useState: (initialState: (() => T) | T) => [T, Dispatch];
useEffect: (callback: (() => void) | void, deps: any[] | void) => void;
+ useRef: (initialValue: T) => { current: T };
};
export type Dispatch = (action: Action) => void;
diff --git a/packages/shared/ReactTypes.ts b/packages/shared/ReactTypes.ts
index 79f3388..f45e4dc 100644
--- a/packages/shared/ReactTypes.ts
+++ b/packages/shared/ReactTypes.ts
@@ -1,4 +1,4 @@
-export type Ref = any;
+export type Ref = { current: any } | ((instance: any) => void);
export type ElementType = any;
export type Key = string | null;
export type Props = {
From a6b332ec1ef40ce9ea3413488d9f497d1c11f68e Mon Sep 17 00:00:00 2001
From: liyigang
Date: Mon, 19 Jun 2023 20:36:28 +0800
Subject: [PATCH 12/19] feat: update lazy
---
packages/react-reconciler/src/beginWork.ts | 31 ++++++-
packages/react-reconciler/src/completeWork.ts | 4 +-
packages/react-reconciler/src/fiber.ts | 19 +++-
.../src/fiberLazyComponent.ts | 3 +
packages/react-reconciler/src/workTags.ts | 4 +-
packages/react/index.ts | 2 +
packages/react/src/lazy.ts | 92 +++++++++++++++++++
packages/shared/ReactSymbols.ts | 4 +
packages/shared/ReactTypes.ts | 42 +++++++++
9 files changed, 196 insertions(+), 5 deletions(-)
create mode 100644 packages/react-reconciler/src/fiberLazyComponent.ts
create mode 100644 packages/react/src/lazy.ts
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index e8a67d8..146fe92 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -1,7 +1,11 @@
-import { Fragment } from 'react-reconciler/src/workTags';
+import { Fragment, LazyComponent } from 'react-reconciler/src/workTags';
import { ReactElement } from 'shared/ReactTypes';
import { mountChildFibers, reconcileChildFibers } from './childFiber';
-import { FiberNode } from './fiber';
+import {
+ FiberNode,
+ createWorkInProgress,
+ resolveLazyComponentTag
+} from './fiber';
import { renderWithHooks } from './fiberHooks';
import { Lane, Lanes, NoLane } from './fiberLanes';
import { processUpdateQueue, UpdateQueue } from './updateQueue';
@@ -12,6 +16,8 @@ import {
HostText
} from './workTags';
import { Ref } from './fiberFlags';
+import { resolveDefaultProps } from './fiberLazyComponent';
+import { LazyComponent as LazyComponentType } from 'react/src/lazy';
export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
if (__LOG__) {
@@ -31,6 +37,8 @@ export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
return updateFunctionComponent(workInProgress, renderLanes);
case Fragment:
return updateFragment(workInProgress, renderLanes);
+ case LazyComponent:
+ return mountLazyComponent(workInProgress, renderLanes);
default:
console.error('beginWork未处理的情况');
return null;
@@ -110,3 +118,22 @@ function markRef(current: FiberNode | null, workInProgress: FiberNode) {
workInProgress.flags |= Ref;
}
}
+
+function mountLazyComponent(workInProgress: FiberNode, renderLanes: Lanes) {
+ const elementType = workInProgress.type;
+
+ const props = workInProgress.pendingProps;
+ const lazyComponent: LazyComponentType = elementType;
+ const payload = lazyComponent._payload;
+ const init = lazyComponent._init;
+
+ const Component = init(payload);
+ // 能到这里说明异步结束了
+ workInProgress.type = Component;
+ const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
+ workInProgress.pendingProps = resolveDefaultProps(Component, props);
+ switch (resolvedTag) {
+ case FunctionComponent:
+ return updateFunctionComponent(workInProgress, renderLanes);
+ }
+}
diff --git a/packages/react-reconciler/src/completeWork.ts b/packages/react-reconciler/src/completeWork.ts
index f8dc673..e352e65 100644
--- a/packages/react-reconciler/src/completeWork.ts
+++ b/packages/react-reconciler/src/completeWork.ts
@@ -12,7 +12,8 @@ import {
FunctionComponent,
HostComponent,
HostRoot,
- HostText
+ HostText,
+ LazyComponent
} from './workTags';
function markRef(fiber: FiberNode) {
@@ -100,6 +101,7 @@ export const completeWork = (workInProgress: FiberNode) => {
case FunctionComponent:
case HostRoot:
case Fragment:
+ case LazyComponent:
bubbleProperties(workInProgress);
return null;
case HostText:
diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts
index 23b35d3..c02fcc8 100644
--- a/packages/react-reconciler/src/fiber.ts
+++ b/packages/react-reconciler/src/fiber.ts
@@ -7,9 +7,11 @@ import {
Fragment,
FunctionComponent,
HostComponent,
- WorkTag
+ WorkTag,
+ LazyComponent
} from './workTags';
import { CallbackNode } from 'scheduler';
+import { REACT_LAZY_TYPE } from 'shared/ReactSymbols';
export class FiberNode {
pendingProps: Props;
@@ -117,6 +119,12 @@ export function createFiberFromElement(
if (typeof type === 'string') {
fiberTag = HostComponent;
+ } else if (typeof type === 'object' && type !== null) {
+ switch (type.$$typeof) {
+ case REACT_LAZY_TYPE:
+ fiberTag = LazyComponent;
+ break;
+ }
} else if (typeof type !== 'function') {
console.error('未定义的type类型', element);
}
@@ -173,3 +181,12 @@ export const createWorkInProgress = (
return wip;
};
+
+// eslint-disable-next-line @typescript-eslint/ban-types
+export function resolveLazyComponentTag(Component: Function): WorkTag {
+ if (typeof Component === 'function') {
+ // 不考虑class
+ return FunctionComponent;
+ }
+ throw '未知的tag';
+}
diff --git a/packages/react-reconciler/src/fiberLazyComponent.ts b/packages/react-reconciler/src/fiberLazyComponent.ts
new file mode 100644
index 0000000..50b3c3f
--- /dev/null
+++ b/packages/react-reconciler/src/fiberLazyComponent.ts
@@ -0,0 +1,3 @@
+export function resolveDefaultProps(Component: any, baseProps: object): object {
+ return baseProps;
+}
diff --git a/packages/react-reconciler/src/workTags.ts b/packages/react-reconciler/src/workTags.ts
index 63a63f9..90b0a1a 100644
--- a/packages/react-reconciler/src/workTags.ts
+++ b/packages/react-reconciler/src/workTags.ts
@@ -3,10 +3,12 @@ export type WorkTag =
| typeof HostRoot
| typeof HostComponent
| typeof HostText
- | typeof Fragment;
+ | typeof Fragment
+ | typeof LazyComponent;
export const FunctionComponent = 0;
export const HostRoot = 3;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
+export const LazyComponent = 16;
diff --git a/packages/react/index.ts b/packages/react/index.ts
index 88e19b0..1885862 100644
--- a/packages/react/index.ts
+++ b/packages/react/index.ts
@@ -5,6 +5,8 @@ import currentDispatcher, {
import { jsx, isValidElement as isValidElementFn } from './src/jsx';
+export { lazy } from './src/lazy';
+
export const useState: Dispatcher['useState'] = (initialState) => {
const dispatcher = resolveDispatcher() as Dispatcher;
return dispatcher.useState(initialState);
diff --git a/packages/react/src/lazy.ts b/packages/react/src/lazy.ts
new file mode 100644
index 0000000..57ba286
--- /dev/null
+++ b/packages/react/src/lazy.ts
@@ -0,0 +1,92 @@
+import { REACT_LAZY_TYPE } from 'shared/ReactSymbols';
+import { Thenable, Wakeable } from 'shared/ReactTypes';
+
+const Uninitialized = -1;
+const Pending = 0;
+const Resolved = 1;
+const Rejected = 2;
+
+type UninitializedPayload = {
+ _status: -1;
+ _result: () => Thenable<{ default: T }>;
+};
+
+type PendingPayload = {
+ _status: 0;
+ _result: Wakeable;
+};
+
+type ResolvedPayload = {
+ _status: 1;
+ _result: { default: T };
+};
+
+type RejectedPayload = {
+ _status: 2;
+ _result: Err;
+};
+
+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();
+ const status = payload._status as number;
+ thenable.then(
+ (moduleObject) => {
+ if (status === Pending || status === Uninitialized) {
+ const resolved = payload as unknown as ResolvedPayload;
+ resolved._status = Resolved;
+ resolved._result = moduleObject;
+ }
+ },
+ (error) => {
+ if (status === Pending || status === Uninitialized) {
+ const rejected = payload as unknown as RejectedPayload;
+ rejected._status = Rejected;
+ rejected._result = error;
+ }
+ }
+ );
+ if (payload._status === Uninitialized) {
+ const pending = payload as unknown as PendingPayload;
+ 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 7608cf6..02c3df8 100644
--- a/packages/shared/ReactSymbols.ts
+++ b/packages/shared/ReactSymbols.ts
@@ -7,3 +7,7 @@ export const REACT_ELEMENT_TYPE = supportSymbol
export const REACT_FRAGMENT_TYPE = supportSymbol
? Symbol.for('react.fragment')
: 0xeacb;
+
+export const REACT_LAZY_TYPE = supportSymbol
+ ? Symbol.for('react.lazy')
+ : 0xead4;
diff --git a/packages/shared/ReactTypes.ts b/packages/shared/ReactTypes.ts
index f45e4dc..5faeaca 100644
--- a/packages/shared/ReactTypes.ts
+++ b/packages/shared/ReactTypes.ts
@@ -16,3 +16,45 @@ export interface ReactElement {
}
export type Action = State | ((prevState: State) => State);
+
+export interface Wakeable {
+ then(
+ onFulfill: () => Thenable,
+ onReject: () => Thenable
+ ): void | Wakeable;
+}
+
+interface ThenableImpl {
+ then(
+ onFulfill: (value: T) => Result,
+ onReject: (error: Err) => Result
+ ): void | Wakeable;
+}
+
+interface UntrackedThenable
+ extends ThenableImpl {
+ status?: void;
+}
+
+export interface PendingThenable
+ extends ThenableImpl {
+ status: 'pending';
+}
+
+export interface FulfilledThenable
+ extends ThenableImpl {
+ status: 'fulfilled';
+ value: T;
+}
+
+export interface RejectedThenable
+ extends ThenableImpl {
+ status: 'rejected';
+ reason: Err;
+}
+
+export type Thenable =
+ | UntrackedThenable
+ | PendingThenable
+ | FulfilledThenable
+ | RejectedThenable;
From 56b9380bcd9ca0645ff685e5b1b9bccb319aa139 Mon Sep 17 00:00:00 2001
From: liyigang
Date: Tue, 20 Jun 2023 21:16:27 +0800
Subject: [PATCH 13/19] feat: update suspense
---
demos/v11/component.tsx | 3 +
demos/v11/main.tsx | 24 ++--
package.json | 2 +-
packages/react-reconciler/src/beginWork.ts | 138 +++++++++++++++++++-
packages/react-reconciler/src/fiber.ts | 8 +-
packages/react-reconciler/src/fiberFlags.ts | 2 +
packages/react-reconciler/src/workLoop.ts | 8 +-
packages/react-reconciler/src/workTags.ts | 2 +
packages/react/index.ts | 2 +
packages/shared/ReactSymbols.ts | 4 +
10 files changed, 172 insertions(+), 21 deletions(-)
create mode 100644 demos/v11/component.tsx
diff --git a/demos/v11/component.tsx b/demos/v11/component.tsx
new file mode 100644
index 0000000..eeb34dd
--- /dev/null
+++ b/demos/v11/component.tsx
@@ -0,0 +1,3 @@
+export default function Comp() {
+ return async component
;
+}
diff --git a/demos/v11/main.tsx b/demos/v11/main.tsx
index e588260..c4c33e8 100644
--- a/demos/v11/main.tsx
+++ b/demos/v11/main.tsx
@@ -1,23 +1,23 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, lazy } from 'react';
import { createRoot } from 'react-dom/client';
+const Comp = lazy(() => import('./component'));
+
function App() {
+ console.log(lazy);
const [num, updateNum] = useState(0);
const len = 8;
+ useEffect(() => {
+ Comp().then((res) => {
+ console.log(res);
+ });
+ }, []);
console.log('num', num);
return (
- {
- updateNum((num: number) => num + 1);
- }}
- >
- {Array(len)
- .fill(1)
- .map((_, i) => {
- return ;
- })}
-
+
+
+
);
}
diff --git a/package.json b/package.json
index c2c2a1b..8badd58 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"license": "MIT",
"scripts": {
"build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
- "demo": "vite serve demos/ref --config demos/vite.config.js --force",
+ "demo": "vite serve demos/v11 --config demos/vite.config.js --force",
"lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages",
"test": "jest --config scripts/jest/jest.config.js"
},
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index 146fe92..d99d132 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -1,13 +1,18 @@
-import { Fragment, LazyComponent } from 'react-reconciler/src/workTags';
-import { ReactElement } from 'shared/ReactTypes';
+import {
+ Fragment,
+ LazyComponent,
+ SuspenseComponent
+} from 'react-reconciler/src/workTags';
+import { Props, ReactElement } from 'shared/ReactTypes';
import { mountChildFibers, reconcileChildFibers } from './childFiber';
import {
FiberNode,
+ createFiberFromFragment,
createWorkInProgress,
resolveLazyComponentTag
} from './fiber';
import { renderWithHooks } from './fiberHooks';
-import { Lane, Lanes, NoLane } from './fiberLanes';
+import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes';
import { processUpdateQueue, UpdateQueue } from './updateQueue';
import {
FunctionComponent,
@@ -15,7 +20,7 @@ import {
HostRoot,
HostText
} from './workTags';
-import { Ref } from './fiberFlags';
+import { Ref, NoFlags, DidCapture } from './fiberFlags';
import { resolveDefaultProps } from './fiberLazyComponent';
import { LazyComponent as LazyComponentType } from 'react/src/lazy';
@@ -39,6 +44,8 @@ export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
return updateFragment(workInProgress, renderLanes);
case LazyComponent:
return mountLazyComponent(workInProgress, renderLanes);
+ case SuspenseComponent:
+ return updateSuspenseComponent(workInProgress, renderLanes);
default:
console.error('beginWork未处理的情况');
return null;
@@ -137,3 +144,126 @@ function mountLazyComponent(workInProgress: FiberNode, renderLanes: Lanes) {
return updateFunctionComponent(workInProgress, renderLanes);
}
}
+
+function updateSuspenseComponent(
+ workInProgress: FiberNode,
+ renderLanes: Lanes
+) {
+ const current = workInProgress.alternate;
+ const nextProps = workInProgress.pendingProps;
+
+ let showFallback = false;
+ const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
+
+ if (didSuspend) {
+ showFallback = true;
+ workInProgress.flags &= ~DidCapture;
+ }
+
+ if (current === null) {
+ const nextPrimaryChildren = nextProps.children;
+ const nextFallbackChildren = nextProps.fallback;
+
+ if (showFallback) {
+ const fallbackFragment = mountSuspenseFallbackChildren(
+ workInProgress,
+ nextPrimaryChildren,
+ nextFallbackChildren,
+ renderLanes
+ );
+ return fallbackFragment;
+ } else {
+ return mountSuspensePrimaryChildren(
+ workInProgress,
+ nextPrimaryChildren,
+ renderLanes
+ );
+ }
+ } else {
+ const nextFallbackChildren = nextProps.fallback;
+ const nextPrimaryChildren = nextProps.children;
+ if (showFallback) {
+ const fallbackChildFragment = updateSuspenseFallbackChildren(
+ workInProgress,
+ nextPrimaryChildren,
+ nextFallbackChildren,
+ renderLanes
+ );
+ return fallbackChildFragment;
+ } else {
+ return updateSuspensePrimaryChildren(
+ workInProgress,
+ nextPrimaryChildren,
+ renderLanes
+ );
+ }
+ }
+}
+
+function mountSuspenseFallbackChildren(
+ workInProgress: FiberNode,
+ primaryChildren: any,
+ fallbackChildren: any,
+ renderLanes: any
+) {
+ const progressedPrimaryFragment: FiberNode | null = workInProgress.child;
+
+ let primaryChildFragment;
+ let fallbackChildFragment;
+
+ if (progressedPrimaryFragment !== null) {
+ primaryChildFragment = progressedPrimaryFragment;
+ primaryChildFragment.childLanes = NoLanes;
+ primaryChildFragment.pendingProps = primaryChildren;
+
+ fallbackChildFragment = createFiberFromFragment(
+ fallbackChildren,
+ renderLanes,
+ null
+ );
+ } else {
+ primaryChildFragment = createFiberFromFragment(
+ primaryChildren,
+ renderLanes,
+ null
+ );
+ fallbackChildFragment = createFiberFromFragment(
+ fallbackChildren,
+ renderLanes,
+ null
+ );
+ }
+
+ primaryChildFragment.return = workInProgress;
+ fallbackChildFragment.return = workInProgress;
+ primaryChildFragment.sibling = fallbackChildFragment;
+ workInProgress.child = primaryChildFragment;
+
+ return fallbackChildFragment;
+}
+function updateSuspenseFallbackChildren(
+ workInProgress: FiberNode,
+ primaryChildren: any,
+ fallbackChildren: any,
+ renderLanes: any
+) {
+ const current = workInProgress.alternate!;
+ const currentPrimaryChildFragment: FiberNode = current.child;
+ const currentFallbackChildFragment: FiberNode | null =
+ currentPrimaryChildFragment.sibling;
+}
+
+function mountSuspensePrimaryChildren(
+ workInProgress: FiberNode,
+ primaryChildren: any,
+ renderLanes: Lanes
+) {
+ const primaryChildFragment = createFiberFromFragment(
+ primaryChildren,
+ renderLanes,
+ null
+ );
+ primaryChildFragment.return = workInProgress;
+ workInProgress.child = primaryChildFragment;
+ return primaryChildFragment;
+}
diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts
index c02fcc8..a61ac3a 100644
--- a/packages/react-reconciler/src/fiber.ts
+++ b/packages/react-reconciler/src/fiber.ts
@@ -8,10 +8,11 @@ import {
FunctionComponent,
HostComponent,
WorkTag,
- LazyComponent
+ LazyComponent,
+ SuspenseComponent
} from './workTags';
import { CallbackNode } from 'scheduler';
-import { REACT_LAZY_TYPE } from 'shared/ReactSymbols';
+import { REACT_LAZY_TYPE, REACT_SUSPENSE_TYPE } from 'shared/ReactSymbols';
export class FiberNode {
pendingProps: Props;
@@ -124,6 +125,9 @@ export function createFiberFromElement(
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
break;
+ case REACT_SUSPENSE_TYPE:
+ fiberTag = SuspenseComponent;
+ break;
}
} else if (typeof type !== 'function') {
console.error('未定义的type类型', element);
diff --git a/packages/react-reconciler/src/fiberFlags.ts b/packages/react-reconciler/src/fiberFlags.ts
index 7ac59c3..4d97764 100644
--- a/packages/react-reconciler/src/fiberFlags.ts
+++ b/packages/react-reconciler/src/fiberFlags.ts
@@ -5,6 +5,8 @@ export const Placement = 0b00000000000000000000000010;
export const Update = 0b00000000000000000000000100;
export const ChildDeletion = 0b00000000000000000000010000;
+export const DidCapture = 0b0000000000000000000010000000;
+
// useEffect
export const PassiveEffect = 0b00000000000000000000100000;
export const Ref = 0b00000000000000000001000000;
diff --git a/packages/react-reconciler/src/workLoop.ts b/packages/react-reconciler/src/workLoop.ts
index ac25f10..5e3ac79 100644
--- a/packages/react-reconciler/src/workLoop.ts
+++ b/packages/react-reconciler/src/workLoop.ts
@@ -212,8 +212,7 @@ function renderRoot(
shouldTimeSlice ? workLoopConcurrent() : workLoopSync();
break;
} catch (e) {
- console.error('workLoop发生错误', e);
- workInProgress = null;
+ handleThrow(root, e);
}
} while (true);
@@ -401,3 +400,8 @@ function completeUnitOfWork(fiber: FiberNode) {
workInProgress = node;
} while (node !== null);
}
+
+function handleThrow(root: FiberRootNode, thrownValue: any): void {
+ console.error('workLoop发生错误', thrownValue, workInProgress);
+ workInProgress = null;
+}
diff --git a/packages/react-reconciler/src/workTags.ts b/packages/react-reconciler/src/workTags.ts
index 90b0a1a..bc9a774 100644
--- a/packages/react-reconciler/src/workTags.ts
+++ b/packages/react-reconciler/src/workTags.ts
@@ -4,6 +4,7 @@ export type WorkTag =
| typeof HostComponent
| typeof HostText
| typeof Fragment
+ | typeof SuspenseComponent
| typeof LazyComponent;
export const FunctionComponent = 0;
@@ -11,4 +12,5 @@ export const HostRoot = 3;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
+export const SuspenseComponent = 13;
export const LazyComponent = 16;
diff --git a/packages/react/index.ts b/packages/react/index.ts
index 1885862..14e414d 100644
--- a/packages/react/index.ts
+++ b/packages/react/index.ts
@@ -7,6 +7,8 @@ import { jsx, isValidElement as isValidElementFn } from './src/jsx';
export { lazy } from './src/lazy';
+export { REACT_SUSPENSE_TYPE as Suspense } from 'shared/ReactSymbols';
+
export const useState: Dispatcher['useState'] = (initialState) => {
const dispatcher = resolveDispatcher() as Dispatcher;
return dispatcher.useState(initialState);
diff --git a/packages/shared/ReactSymbols.ts b/packages/shared/ReactSymbols.ts
index 02c3df8..10434e4 100644
--- a/packages/shared/ReactSymbols.ts
+++ b/packages/shared/ReactSymbols.ts
@@ -11,3 +11,7 @@ export const REACT_FRAGMENT_TYPE = supportSymbol
export const REACT_LAZY_TYPE = supportSymbol
? Symbol.for('react.lazy')
: 0xead4;
+
+export const REACT_SUSPENSE_TYPE: symbol = supportSymbol
+ ? Symbol.for('react.suspense')
+ : 0xead1;
From c6506f88840b966ece4c1fd5b98d45cd63bd3fb1 Mon Sep 17 00:00:00 2001
From: liyigang
Date: Wed, 21 Jun 2023 21:12:11 +0800
Subject: [PATCH 14/19] feat: update suspense
---
demos/suspense/component.tsx | 3 +
demos/suspense/index.html | 16 ++
demos/suspense/main.tsx | 36 +++++
demos/suspense/vite-env.d.ts | 1 +
demos/v11/main.tsx | 24 +--
packages/react-reconciler/src/beginWork.ts | 144 +++++++-----------
packages/react-reconciler/src/commitWork.ts | 54 ++++++-
packages/react-reconciler/src/completeWork.ts | 9 +-
packages/react-reconciler/src/fiber.ts | 5 +-
packages/react-reconciler/src/fiberThrow.ts | 24 +++
packages/react-reconciler/src/workLoop.ts | 48 +++++-
packages/shared/ReactTypes.ts | 8 +-
12 files changed, 254 insertions(+), 118 deletions(-)
create mode 100644 demos/suspense/component.tsx
create mode 100644 demos/suspense/index.html
create mode 100644 demos/suspense/main.tsx
create mode 100644 demos/suspense/vite-env.d.ts
create mode 100644 packages/react-reconciler/src/fiberThrow.ts
diff --git a/demos/suspense/component.tsx b/demos/suspense/component.tsx
new file mode 100644
index 0000000..eeb34dd
--- /dev/null
+++ b/demos/suspense/component.tsx
@@ -0,0 +1,3 @@
+export default function Comp() {
+ return async component
;
+}
diff --git a/demos/suspense/index.html b/demos/suspense/index.html
new file mode 100644
index 0000000..8869d84
--- /dev/null
+++ b/demos/suspense/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Suspense
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/suspense/main.tsx b/demos/suspense/main.tsx
new file mode 100644
index 0000000..780a5b2
--- /dev/null
+++ b/demos/suspense/main.tsx
@@ -0,0 +1,36 @@
+import { useState, useEffect, lazy, Suspense } from 'react';
+import { createRoot } from 'react-dom/client';
+
+const delay = (t: number) =>
+ new Promise((r) => {
+ setTimeout(r, t);
+ });
+
+const Comp = lazy(() =>
+ import('./component').then((res) => {
+ return delay(3000).then(() => {
+ console.log('ready render Comp');
+ return res;
+ });
+ })
+);
+
+function App() {
+ const [num, setNum] = useState(0);
+ console.log('num', num);
+ return (
+
+
+ loading...
}>
+
+
+
+
+ );
+}
+
+function Child({ i }) {
+ return i am child {i}
;
+}
+
+createRoot(document.getElementById('root') as HTMLElement).render();
diff --git a/demos/suspense/vite-env.d.ts b/demos/suspense/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/demos/suspense/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/demos/v11/main.tsx b/demos/v11/main.tsx
index c4c33e8..e588260 100644
--- a/demos/v11/main.tsx
+++ b/demos/v11/main.tsx
@@ -1,23 +1,23 @@
-import { useState, useEffect, lazy } from 'react';
+import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
-const Comp = lazy(() => import('./component'));
-
function App() {
- console.log(lazy);
const [num, updateNum] = useState(0);
const len = 8;
- useEffect(() => {
- Comp().then((res) => {
- console.log(res);
- });
- }, []);
console.log('num', num);
return (
-
-
-
+ {
+ updateNum((num: number) => num + 1);
+ }}
+ >
+ {Array(len)
+ .fill(1)
+ .map((_, i) => {
+ return ;
+ })}
+
);
}
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index d99d132..4b821ba 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -20,7 +20,13 @@ import {
HostRoot,
HostText
} from './workTags';
-import { Ref, NoFlags, DidCapture } from './fiberFlags';
+import {
+ Ref,
+ NoFlags,
+ DidCapture,
+ Placement,
+ ChildDeletion
+} from './fiberFlags';
import { resolveDefaultProps } from './fiberLazyComponent';
import { LazyComponent as LazyComponentType } from 'react/src/lazy';
@@ -142,6 +148,8 @@ function mountLazyComponent(workInProgress: FiberNode, renderLanes: Lanes) {
switch (resolvedTag) {
case FunctionComponent:
return updateFunctionComponent(workInProgress, renderLanes);
+ default:
+ return null;
}
}
@@ -159,111 +167,69 @@ function updateSuspenseComponent(
showFallback = true;
workInProgress.flags &= ~DidCapture;
}
+console.log('updateSuspenseComponent')
+ const nextPrimaryChildren = nextProps.children;
+ const nextFallbackChildren = nextProps.fallback;
if (current === null) {
- const nextPrimaryChildren = nextProps.children;
- const nextFallbackChildren = nextProps.fallback;
-
if (showFallback) {
- const fallbackFragment = mountSuspenseFallbackChildren(
- workInProgress,
- nextPrimaryChildren,
+ const fallbackFragment = createFiberFromFragment(
nextFallbackChildren,
- renderLanes
+ renderLanes,
+ null
);
+ workInProgress.child = fallbackFragment;
+ fallbackFragment.return = workInProgress;
+ console.log('fallbackFragment', fallbackFragment);
return fallbackFragment;
} else {
- return mountSuspensePrimaryChildren(
- workInProgress,
+ const primaryFragment = createFiberFromFragment(
nextPrimaryChildren,
- renderLanes
+ renderLanes,
+ null
);
+
+ primaryFragment.flags |= Placement;
+ workInProgress.child = primaryFragment;
+ primaryFragment.return = workInProgress;
+ console.log('primaryFragment', primaryFragment);
+
+ return primaryFragment;
}
} else {
- const nextFallbackChildren = nextProps.fallback;
- const nextPrimaryChildren = nextProps.children;
if (showFallback) {
- const fallbackChildFragment = updateSuspenseFallbackChildren(
- workInProgress,
- nextPrimaryChildren,
+ const fallbackFragment = createFiberFromFragment(
nextFallbackChildren,
- renderLanes
+ renderLanes,
+ null
);
- return fallbackChildFragment;
+ workInProgress.child = fallbackFragment;
+ fallbackFragment.return = workInProgress;
+ fallbackFragment.flags |= Placement;
+ console.log('fallbackFragment', fallbackFragment);
+ return fallbackFragment;
} else {
- return updateSuspensePrimaryChildren(
- workInProgress,
+ const primaryFragment = createFiberFromFragment(
nextPrimaryChildren,
- renderLanes
+ renderLanes,
+ null
);
- }
- }
-}
-
-function mountSuspenseFallbackChildren(
- workInProgress: FiberNode,
- primaryChildren: any,
- fallbackChildren: any,
- renderLanes: any
-) {
- const progressedPrimaryFragment: FiberNode | null = workInProgress.child;
-
- let primaryChildFragment;
- let fallbackChildFragment;
- if (progressedPrimaryFragment !== null) {
- primaryChildFragment = progressedPrimaryFragment;
- primaryChildFragment.childLanes = NoLanes;
- primaryChildFragment.pendingProps = primaryChildren;
-
- fallbackChildFragment = createFiberFromFragment(
- fallbackChildren,
- renderLanes,
- null
- );
- } else {
- primaryChildFragment = createFiberFromFragment(
- primaryChildren,
- renderLanes,
- null
- );
- fallbackChildFragment = createFiberFromFragment(
- fallbackChildren,
- renderLanes,
- null
- );
+ primaryFragment.flags |= Placement;
+ if (workInProgress.child) {
+ if (workInProgress.deletions !== null) {
+ workInProgress.deletions.push(workInProgress.child!);
+ } else {
+ workInProgress.deletions = [workInProgress.child!];
+ }
+ workInProgress.flags |= ChildDeletion;
+ }
+
+ workInProgress.child = primaryFragment;
+ primaryFragment.return = workInProgress;
+ console.log('primaryFragment', primaryFragment);
+
+ return primaryFragment;
+ }
}
-
- primaryChildFragment.return = workInProgress;
- fallbackChildFragment.return = workInProgress;
- primaryChildFragment.sibling = fallbackChildFragment;
- workInProgress.child = primaryChildFragment;
-
- return fallbackChildFragment;
-}
-function updateSuspenseFallbackChildren(
- workInProgress: FiberNode,
- primaryChildren: any,
- fallbackChildren: any,
- renderLanes: any
-) {
- const current = workInProgress.alternate!;
- const currentPrimaryChildFragment: FiberNode = current.child;
- const currentFallbackChildFragment: FiberNode | null =
- currentPrimaryChildFragment.sibling;
-}
-
-function mountSuspensePrimaryChildren(
- workInProgress: FiberNode,
- primaryChildren: any,
- renderLanes: Lanes
-) {
- const primaryChildFragment = createFiberFromFragment(
- primaryChildren,
- renderLanes,
- null
- );
- primaryChildFragment.return = workInProgress;
- workInProgress.child = primaryChildFragment;
- return primaryChildFragment;
}
diff --git a/packages/react-reconciler/src/commitWork.ts b/packages/react-reconciler/src/commitWork.ts
index d7f8cd5..2398070 100644
--- a/packages/react-reconciler/src/commitWork.ts
+++ b/packages/react-reconciler/src/commitWork.ts
@@ -25,8 +25,17 @@ import {
FunctionComponent,
HostComponent,
HostRoot,
- HostText
+ HostText,
+ SuspenseComponent
} from './workTags';
+import { RetryQueue } from './fiberThrow';
+import { Wakeable } from 'shared/ReactTypes';
+import { SyncLane } from './fiberLanes';
+import {
+ ensureRootIsScheduled,
+ markRootUpdated,
+ markUpdateLaneFromFiberToRoot
+} from './workLoop';
let nextEffect: FiberNode | null = null;
@@ -84,8 +93,16 @@ const commitMutationEffectsOnFiber = (
finishedWork.flags &= ~ChildDeletion;
}
if ((flags & Update) !== NoFlags) {
- commitUpdate(finishedWork);
finishedWork.flags &= ~Update;
+ if (tag === SuspenseComponent) {
+ const retryQueue = finishedWork.updateQueue as RetryQueue;
+ if (retryQueue !== null) {
+ finishedWork.updateQueue = null;
+ attachSuspenseRetryListeners(finishedWork, retryQueue);
+ }
+ } else {
+ commitUpdate(finishedWork);
+ }
}
if ((flags & PassiveEffect) !== NoFlags) {
// 收集因deps变化而需要执行的useEffect
@@ -403,3 +420,36 @@ export function commitHookEffectListMount(flags: Flags, lastEffect: Effect) {
}
});
}
+
+function getRetryCache(finishedWork: FiberNode) {
+ switch (finishedWork.tag) {
+ case SuspenseComponent:
+ let retryCache = finishedWork.stateNode;
+ if (retryCache === null) {
+ retryCache = finishedWork.stateNode = new WeakSet();
+ }
+ return retryCache;
+ }
+}
+
+function resolveRetryWakeable(boundaryFiber: FiberNode) {
+ const root = markUpdateLaneFromFiberToRoot(boundaryFiber, SyncLane);
+ if (root !== null) {
+ markRootUpdated(root, SyncLane);
+ ensureRootIsScheduled(root);
+ }
+}
+
+function attachSuspenseRetryListeners(
+ finishedWork: FiberNode,
+ wakeables: RetryQueue
+) {
+ const retryCache = getRetryCache(finishedWork);
+ wakeables.forEach((wakeable) => {
+ const retry = resolveRetryWakeable.bind(null, finishedWork);
+ if (!retryCache.has(wakeable)) {
+ retryCache.add(wakeable);
+ wakeable.then(retry, retry);
+ }
+ });
+}
diff --git a/packages/react-reconciler/src/completeWork.ts b/packages/react-reconciler/src/completeWork.ts
index e352e65..ad3d829 100644
--- a/packages/react-reconciler/src/completeWork.ts
+++ b/packages/react-reconciler/src/completeWork.ts
@@ -13,8 +13,10 @@ import {
HostComponent,
HostRoot,
HostText,
- LazyComponent
+ LazyComponent,
+ SuspenseComponent
} from './workTags';
+import { RetryQueue } from './fiberThrow';
function markRef(fiber: FiberNode) {
fiber.flags |= Ref;
@@ -121,6 +123,11 @@ export const completeWork = (workInProgress: FiberNode) => {
// 冒泡flag
bubbleProperties(workInProgress);
return null;
+ case SuspenseComponent:
+ const retryQueue = workInProgress.updateQueue as RetryQueue | null;
+ if (retryQueue !== null) workInProgress.flags |= Update;
+ bubbleProperties(workInProgress);
+ return null;
default:
console.error('completeWork未定义的fiber.tag', workInProgress);
return null;
diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts
index a61ac3a..a2908cd 100644
--- a/packages/react-reconciler/src/fiber.ts
+++ b/packages/react-reconciler/src/fiber.ts
@@ -125,10 +125,9 @@ export function createFiberFromElement(
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
break;
- case REACT_SUSPENSE_TYPE:
- fiberTag = SuspenseComponent;
- break;
}
+ } else if (type === REACT_SUSPENSE_TYPE) {
+ fiberTag = SuspenseComponent;
} else if (typeof type !== 'function') {
console.error('未定义的type类型', element);
}
diff --git a/packages/react-reconciler/src/fiberThrow.ts b/packages/react-reconciler/src/fiberThrow.ts
new file mode 100644
index 0000000..f49534c
--- /dev/null
+++ b/packages/react-reconciler/src/fiberThrow.ts
@@ -0,0 +1,24 @@
+import { Wakeable } from 'shared/ReactTypes';
+import { FiberNode } from './fiber';
+import { DidCapture } from './fiberFlags';
+
+export type RetryQueue = Set>;
+
+export function throwException(unitOfWork: FiberNode, value: any) {
+ if (
+ value !== null &&
+ typeof value === 'object' &&
+ typeof value.then === 'function'
+ ) {
+ const weakable: Wakeable = value;
+ // 为了简化 假设一定是Suspense包裹一层lazy,
+ const suspenseBoundary = unitOfWork!.return!.return!;
+ suspenseBoundary.flags |= DidCapture;
+ const retryQueue = suspenseBoundary.updateQueue as RetryQueue | null;
+ if (retryQueue === null) {
+ suspenseBoundary.updateQueue = new Set([weakable]);
+ } else {
+ retryQueue.add(weakable);
+ }
+ }
+}
diff --git a/packages/react-reconciler/src/workLoop.ts b/packages/react-reconciler/src/workLoop.ts
index 5e3ac79..f648fbc 100644
--- a/packages/react-reconciler/src/workLoop.ts
+++ b/packages/react-reconciler/src/workLoop.ts
@@ -13,7 +13,7 @@ import {
FiberRootNode,
PendingPassiveEffects
} from './fiber';
-import { MutationMask, NoFlags, PassiveMask } from './fiberFlags';
+import { MutationMask, NoFlags, PassiveMask, DidCapture } from './fiberFlags';
import {
getHighestPriorityLane,
getNextLanes,
@@ -31,6 +31,7 @@ import { flushSyncCallbacks, scheduleSyncCallback } from './syncTaskQueue';
import { HostRoot } from './workTags';
import * as scheduler from 'scheduler';
import { HookHasEffect, Passive } from './hookEffectTags';
+import { throwException } from './fiberThrow';
const {
unstable_scheduleCallback: scheduleCallback,
@@ -58,6 +59,15 @@ const RootCompleted = 2;
// 与调度effect相关
let rootDoesHavePassiveEffects = false;
+// Suspense
+type SuspendedReason =
+ | typeof NotSuspended
+ | typeof SuspendedOnDeprecatedThrowPromise;
+const NotSuspended = 0;
+const SuspendedOnDeprecatedThrowPromise = 6;
+let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
+let workInProgressThrownValue: any = null;
+
export function scheduleUpdateOnFiber(fiber: FiberNode, lane: Lane) {
if (__LOG__) {
console.log('开始schedule阶段', fiber, lane);
@@ -72,11 +82,11 @@ export function scheduleUpdateOnFiber(fiber: FiberNode, lane: Lane) {
ensureRootIsScheduled(root);
}
-function markRootUpdated(root: FiberRootNode, lane: Lane) {
+export function markRootUpdated(root: FiberRootNode, lane: Lane) {
root.pendingLanes = mergeLanes(root.pendingLanes, lane);
}
-function markUpdateLaneFromFiberToRoot(fiber: FiberNode, lane: Lane) {
+export function markUpdateLaneFromFiberToRoot(fiber: FiberNode, lane: Lane) {
let node = fiber;
let parent = node.return;
@@ -96,7 +106,7 @@ function markUpdateLaneFromFiberToRoot(fiber: FiberNode, lane: Lane) {
return null;
}
-function ensureRootIsScheduled(root: FiberRootNode) {
+export function ensureRootIsScheduled(root: FiberRootNode) {
const updateLanes = getNextLanes(root);
const existingCallback = root.callbackNode;
@@ -110,7 +120,6 @@ function ensureRootIsScheduled(root: FiberRootNode) {
}
const curPriority = getHighestPriorityLane(updateLanes);
const prevPriority = root.callbackPriority;
-
if (curPriority === prevPriority) {
// 有更新在进行,比较该更新与正在进行的更新的优先级
// 如果优先级相同,则不需要调度新的,退出调度
@@ -209,6 +218,18 @@ function renderRoot(
// render阶段具体操作
do {
try {
+ if (
+ workInProgressSuspendedReason !== NotSuspended &&
+ workInProgress !== null
+ ) {
+ const unitOfWork = workInProgress;
+ const thrownValue = workInProgressThrownValue;
+
+ workInProgressSuspendedReason = NotSuspended;
+ throwAndUnwindWorkLoop(unitOfWork, thrownValue);
+
+ workInProgress = workInProgress!.return!.return;
+ }
shouldTimeSlice ? workLoopConcurrent() : workLoopSync();
break;
} catch (e) {
@@ -402,6 +423,19 @@ function completeUnitOfWork(fiber: FiberNode) {
}
function handleThrow(root: FiberRootNode, thrownValue: any): void {
- console.error('workLoop发生错误', thrownValue, workInProgress);
- workInProgress = null;
+ console.error('handleThrow', thrownValue, workInProgress);
+ workInProgressThrownValue = thrownValue;
+ if (
+ thrownValue &&
+ thrownValue.then &&
+ typeof thrownValue.then === 'function'
+ ) {
+ workInProgressSuspendedReason = SuspendedOnDeprecatedThrowPromise;
+ } else {
+ workInProgress = null;
+ }
+}
+
+function throwAndUnwindWorkLoop(unitOfWork: FiberNode, thrownValue: any) {
+ throwException(unitOfWork, thrownValue);
}
diff --git a/packages/shared/ReactTypes.ts b/packages/shared/ReactTypes.ts
index 5faeaca..864cd74 100644
--- a/packages/shared/ReactTypes.ts
+++ b/packages/shared/ReactTypes.ts
@@ -17,11 +17,11 @@ export interface ReactElement {
export type Action = State | ((prevState: State) => State);
-export interface Wakeable {
+export interface Wakeable {
then(
- onFulfill: () => Thenable,
- onReject: () => Thenable
- ): void | Wakeable;
+ onFulfill: () => Result,
+ onReject: () => Result
+ ): void | Wakeable;
}
interface ThenableImpl {
From 6eafc7a9b6b23e7235366e213ce5c8b86f8b2c0f Mon Sep 17 00:00:00 2001
From: liyigang
Date: Wed, 21 Jun 2023 21:17:44 +0800
Subject: [PATCH 15/19] feat: update comment
---
demos/v11/component.tsx | 3 ---
packages/react-reconciler/src/beginWork.ts | 3 +--
packages/react-reconciler/src/completeWork.ts | 4 +++-
3 files changed, 4 insertions(+), 6 deletions(-)
delete mode 100644 demos/v11/component.tsx
diff --git a/demos/v11/component.tsx b/demos/v11/component.tsx
deleted file mode 100644
index eeb34dd..0000000
--- a/demos/v11/component.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Comp() {
- return async component
;
-}
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index 4b821ba..104dfc4 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -167,7 +167,6 @@ function updateSuspenseComponent(
showFallback = true;
workInProgress.flags &= ~DidCapture;
}
-console.log('updateSuspenseComponent')
const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
@@ -214,7 +213,7 @@ console.log('updateSuspenseComponent')
renderLanes,
null
);
-
+
primaryFragment.flags |= Placement;
if (workInProgress.child) {
if (workInProgress.deletions !== null) {
diff --git a/packages/react-reconciler/src/completeWork.ts b/packages/react-reconciler/src/completeWork.ts
index ad3d829..d609422 100644
--- a/packages/react-reconciler/src/completeWork.ts
+++ b/packages/react-reconciler/src/completeWork.ts
@@ -125,7 +125,9 @@ export const completeWork = (workInProgress: FiberNode) => {
return null;
case SuspenseComponent:
const retryQueue = workInProgress.updateQueue as RetryQueue | null;
- if (retryQueue !== null) workInProgress.flags |= Update;
+ if (retryQueue !== null) {
+ workInProgress.flags |= Update;
+ }
bubbleProperties(workInProgress);
return null;
default:
From 8a89d1e00fef15c9dd7af8b23b4fde0c1df67831 Mon Sep 17 00:00:00 2001
From: liyigang
Date: Wed, 21 Jun 2023 21:20:42 +0800
Subject: [PATCH 16/19] feat: update comment
---
packages/react-reconciler/src/beginWork.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index 104dfc4..7946403 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -177,6 +177,7 @@ function updateSuspenseComponent(
renderLanes,
null
);
+ fallbackFragment.flags |= Placement;
workInProgress.child = fallbackFragment;
fallbackFragment.return = workInProgress;
console.log('fallbackFragment', fallbackFragment);
@@ -205,7 +206,6 @@ function updateSuspenseComponent(
workInProgress.child = fallbackFragment;
fallbackFragment.return = workInProgress;
fallbackFragment.flags |= Placement;
- console.log('fallbackFragment', fallbackFragment);
return fallbackFragment;
} else {
const primaryFragment = createFiberFromFragment(
@@ -226,7 +226,6 @@ function updateSuspenseComponent(
workInProgress.child = primaryFragment;
primaryFragment.return = workInProgress;
- console.log('primaryFragment', primaryFragment);
return primaryFragment;
}
From c069dcc6e295acbb04c94064c3a6b6d5c59443cf Mon Sep 17 00:00:00 2001
From: liyigang
Date: Wed, 21 Jun 2023 21:20:53 +0800
Subject: [PATCH 17/19] feat: update comment
---
packages/react-reconciler/src/beginWork.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index 7946403..f8462a8 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -170,6 +170,7 @@ function updateSuspenseComponent(
const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
+ // 源码中会用Offline去保存状态
if (current === null) {
if (showFallback) {
const fallbackFragment = createFiberFromFragment(
From 83280595cdec6007fb2f45711f5ae75abcceb0d5 Mon Sep 17 00:00:00 2001
From: liyigang
Date: Wed, 21 Jun 2023 21:21:27 +0800
Subject: [PATCH 18/19] feat: update comment
---
packages/react-reconciler/src/beginWork.ts | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index f8462a8..ada31b8 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -181,7 +181,7 @@ function updateSuspenseComponent(
fallbackFragment.flags |= Placement;
workInProgress.child = fallbackFragment;
fallbackFragment.return = workInProgress;
- console.log('fallbackFragment', fallbackFragment);
+
return fallbackFragment;
} else {
const primaryFragment = createFiberFromFragment(
@@ -193,7 +193,6 @@ function updateSuspenseComponent(
primaryFragment.flags |= Placement;
workInProgress.child = primaryFragment;
primaryFragment.return = workInProgress;
- console.log('primaryFragment', primaryFragment);
return primaryFragment;
}
@@ -214,7 +213,7 @@ function updateSuspenseComponent(
renderLanes,
null
);
-
+
primaryFragment.flags |= Placement;
if (workInProgress.child) {
if (workInProgress.deletions !== null) {
From 88501a27312a1266f52e1c1223810ec9b8b47145 Mon Sep 17 00:00:00 2001
From: liyigang
Date: Tue, 27 Jun 2023 20:40:12 +0800
Subject: [PATCH 19/19] feat: update offscreen
---
demos/suspense/component.tsx | 17 +-
demos/suspense/main.tsx | 2 +-
packages/react-reconciler/src/beginWork.ts | 205 ++++++++++++++----
packages/react-reconciler/src/childFiber.ts | 20 +-
packages/react-reconciler/src/completeWork.ts | 9 +-
packages/react-reconciler/src/fiber.ts | 24 +-
packages/react-reconciler/src/fiberFlags.ts | 1 +
.../src/fiberOffscreenComponent.ts | 15 ++
packages/react-reconciler/src/workTags.ts | 15 +-
packages/shared/ReactSymbols.ts | 6 +-
packages/shared/ReactTypes.ts | 2 +
11 files changed, 270 insertions(+), 46 deletions(-)
create mode 100644 packages/react-reconciler/src/fiberOffscreenComponent.ts
diff --git a/demos/suspense/component.tsx b/demos/suspense/component.tsx
index eeb34dd..278e6f9 100644
--- a/demos/suspense/component.tsx
+++ b/demos/suspense/component.tsx
@@ -1,3 +1,18 @@
+import { useState, useEffect } from 'react';
+
export default function Comp() {
- return async component
;
+ const [v, setv] = useState(1);
+ useEffect(() => {
+ console.log('acomp, ', v);
+ }, [v]);
+ return (
+ {
+ console.log('acomp, click');
+ setv(v + 1);
+ }}
+ >
+ async component - {v}
+
+ );
}
diff --git a/demos/suspense/main.tsx b/demos/suspense/main.tsx
index 780a5b2..281b292 100644
--- a/demos/suspense/main.tsx
+++ b/demos/suspense/main.tsx
@@ -8,7 +8,7 @@ const delay = (t: number) =>
const Comp = lazy(() =>
import('./component').then((res) => {
- return delay(3000).then(() => {
+ return delay(1000).then(() => {
console.log('ready render Comp');
return res;
});
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index ada31b8..8f59d8a 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -1,6 +1,7 @@
import {
Fragment,
LazyComponent,
+ OffscreenComponent,
SuspenseComponent
} from 'react-reconciler/src/workTags';
import { Props, ReactElement } from 'shared/ReactTypes';
@@ -8,6 +9,7 @@ import { mountChildFibers, reconcileChildFibers } from './childFiber';
import {
FiberNode,
createFiberFromFragment,
+ createFiberFromOffscreen,
createWorkInProgress,
resolveLazyComponentTag
} from './fiber';
@@ -29,6 +31,8 @@ import {
} from './fiberFlags';
import { resolveDefaultProps } from './fiberLazyComponent';
import { LazyComponent as LazyComponentType } from 'react/src/lazy';
+import { jsx } from 'react/src/jsx';
+import { OffscreenProps } from './fiberOffscreenComponent';
export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
if (__LOG__) {
@@ -52,6 +56,8 @@ export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
return mountLazyComponent(workInProgress, renderLanes);
case SuspenseComponent:
return updateSuspenseComponent(workInProgress, renderLanes);
+ case OffscreenComponent:
+ return updateOffscreenComponent(workInProgress, renderLanes);
default:
console.error('beginWork未处理的情况');
return null;
@@ -173,61 +179,184 @@ function updateSuspenseComponent(
// 源码中会用Offline去保存状态
if (current === null) {
if (showFallback) {
- const fallbackFragment = createFiberFromFragment(
+ const fallbackFragment = mountSuspenseFallbackChildren(
+ workInProgress,
+ nextPrimaryChildren,
nextFallbackChildren,
- renderLanes,
- null
+ renderLanes
);
- fallbackFragment.flags |= Placement;
- workInProgress.child = fallbackFragment;
- fallbackFragment.return = workInProgress;
-
return fallbackFragment;
} else {
- const primaryFragment = createFiberFromFragment(
+ return mountSuspensePrimaryChildren(
+ workInProgress,
nextPrimaryChildren,
- renderLanes,
- null
+ renderLanes
);
-
- primaryFragment.flags |= Placement;
- workInProgress.child = primaryFragment;
- primaryFragment.return = workInProgress;
-
- return primaryFragment;
}
} else {
if (showFallback) {
- const fallbackFragment = createFiberFromFragment(
+ const fallbackChildFragment = updateSuspenseFallbackChildren(
+ workInProgress,
+ nextPrimaryChildren,
nextFallbackChildren,
- renderLanes,
- null
+ renderLanes
);
- workInProgress.child = fallbackFragment;
- fallbackFragment.return = workInProgress;
- fallbackFragment.flags |= Placement;
- return fallbackFragment;
+ return fallbackChildFragment;
} else {
- const primaryFragment = createFiberFromFragment(
+ return updateSuspensePrimaryChildren(
+ workInProgress,
nextPrimaryChildren,
- renderLanes,
- null
+ renderLanes
);
+ }
+ }
+}
+
+function mountSuspenseFallbackChildren(
+ workInProgress: FiberNode,
+ primaryChildren: any,
+ fallbackChildren: any,
+ renderLanes: Lanes
+) {
+ const primaryChildProps: OffscreenProps = {
+ mode: 'hidden',
+ children: primaryChildren
+ };
+ const primaryChildFragment = mountWorkInProgressOffscreenFiber(
+ primaryChildProps,
+ NoLanes
+ );
+ const fallbackFragment = createFiberFromFragment(
+ fallbackChildren,
+ renderLanes,
+ null
+ );
+ primaryChildFragment.return = fallbackFragment.return = workInProgress;
+ primaryChildFragment.sibling = fallbackFragment;
+ workInProgress.child = primaryChildFragment;
+ return fallbackFragment;
+}
+
+function mountSuspensePrimaryChildren(
+ workInProgress: FiberNode,
+ primaryChildren: any,
+ renderLanes: Lanes
+) {
+ const primaryChildProps: OffscreenProps = {
+ mode: 'visible',
+ children: primaryChildren
+ };
+ const primaryChildFragment = mountWorkInProgressOffscreenFiber(
+ primaryChildProps,
+ renderLanes
+ );
+ primaryChildFragment.return = workInProgress;
+ workInProgress.child = primaryChildFragment;
+ return primaryChildFragment;
+}
+function updateSuspenseFallbackChildren(
+ workInProgress: FiberNode,
+ primaryChildren: any,
+ fallbackChildren: any,
+ renderLanes: Lanes
+) {
+ const current = workInProgress.alternate!;
+ const currentPrimaryChildFragment = current.child as FiberNode;
+ const currentFallbackChildFragment: FiberNode | null =
+ currentPrimaryChildFragment.sibling;
+ const primaryChildProps: OffscreenProps = {
+ mode: 'hidden',
+ children: primaryChildren
+ };
+ const primaryChildFragment = updateWorkInProgressOffscreenFiber(
+ currentPrimaryChildFragment,
+ primaryChildProps
+ );
+ let fallbackChildFragment!: FiberNode;
+ if (currentFallbackChildFragment !== null) {
+ fallbackChildFragment = createWorkInProgress(
+ currentFallbackChildFragment,
+ fallbackChildren
+ );
+ } else {
+ fallbackChildFragment = createFiberFromFragment(
+ fallbackChildren,
+ renderLanes,
+ null
+ );
+ fallbackChildFragment.flags |= Placement;
+ }
- primaryFragment.flags |= Placement;
- if (workInProgress.child) {
- if (workInProgress.deletions !== null) {
- workInProgress.deletions.push(workInProgress.child!);
- } else {
- workInProgress.deletions = [workInProgress.child!];
- }
- workInProgress.flags |= ChildDeletion;
- }
+ workInProgress.deletions = null;
+ workInProgress.flags &= ~ChildDeletion;
- workInProgress.child = primaryFragment;
- primaryFragment.return = workInProgress;
+ fallbackChildFragment.return = workInProgress;
+ primaryChildFragment.return = workInProgress;
+ primaryChildFragment.sibling = fallbackChildFragment;
+ workInProgress.child = primaryChildFragment;
- return primaryFragment;
+ return fallbackChildFragment;
+}
+
+function updateSuspensePrimaryChildren(
+ workInProgress: FiberNode,
+ primaryChildren: any,
+ renderLanes: Lanes
+) {
+ const current = workInProgress.alternate!;
+ const currentPrimaryChildFragment = current.child as FiberNode;
+ const currentFallbackChildFragment = currentPrimaryChildFragment.sibling;
+
+ const primaryChildFragment = updateWorkInProgressOffscreenFiber(
+ currentPrimaryChildFragment,
+ {
+ mode: 'visible',
+ children: primaryChildren
}
+ );
+
+ primaryChildFragment.return = workInProgress;
+ primaryChildFragment.sibling = null;
+
+ if (currentFallbackChildFragment !== null) {
+ const deletions = workInProgress.deletions;
+ if (deletions === null) {
+ workInProgress.deletions = [currentFallbackChildFragment];
+ workInProgress.flags |= ChildDeletion;
+ } else {
+ deletions.push(currentFallbackChildFragment);
+ }
+ }
+
+ workInProgress.child = primaryChildFragment;
+ return primaryChildFragment;
+}
+
+function mountWorkInProgressOffscreenFiber(
+ offscreenProps: OffscreenProps,
+ renderLanes: Lanes
+) {
+ return createFiberFromOffscreen(offscreenProps, renderLanes, null);
+}
+
+function updateWorkInProgressOffscreenFiber(
+ current: FiberNode,
+ offscreenProps: OffscreenProps
+) {
+ return createWorkInProgress(current, offscreenProps);
+}
+
+function updateOffscreenComponent(
+ workInProgress: FiberNode,
+ renderLanes: Lanes
+) {
+ // debugger;
+ const nextProps: OffscreenProps = workInProgress.pendingProps;
+ const nextChildren = nextProps.children;
+ if (nextProps.mode === 'hidden') {
+ return null;
+ } else {
+ reconcileChildren(workInProgress, nextChildren, renderLanes);
+ return workInProgress.child;
}
}
diff --git a/packages/react-reconciler/src/childFiber.ts b/packages/react-reconciler/src/childFiber.ts
index 4933e83..8d3e566 100644
--- a/packages/react-reconciler/src/childFiber.ts
+++ b/packages/react-reconciler/src/childFiber.ts
@@ -1,4 +1,8 @@
-import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
+import {
+ REACT_ELEMENT_TYPE,
+ REACT_FRAGMENT_TYPE,
+ REACT_LAZY_TYPE
+} from 'shared/ReactSymbols';
import { Props, ReactElement } from 'shared/ReactTypes';
import {
createFiberFromElement,
@@ -9,6 +13,7 @@ import {
import { ChildDeletion, Placement } from './fiberFlags';
import { Lanes } from './fiberLanes';
import { Fragment, HostText } from './workTags';
+import { LazyComponent } from 'react/src/lazy';
/**
* mount/reconcile只负责 Placement(插入)/Placement(移动)/ChildDeletion(删除)
@@ -17,6 +22,12 @@ import { Fragment, HostText } from './workTags';
type ExistingChildren = Map;
+function resolveLazy(lazyType: LazyComponent) {
+ const payload = lazyType._payload;
+ const init = lazyType._init;
+ return init(payload);
+}
+
function ChildReconciler(shouldTrackEffects: boolean) {
function deleteChild(returnFiber: FiberNode, childToDelete: FiberNode) {
if (!shouldTrackEffects) {
@@ -60,7 +71,12 @@ function ChildReconciler(shouldTrackEffects: boolean) {
// key相同,比较type
if (element.$$typeof === REACT_ELEMENT_TYPE) {
- if (current.type === element.type) {
+ if (
+ current.type === element.type ||
+ (typeof element.type === 'object' &&
+ element.type.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(element.type) === currentFirstChild?.type)
+ ) {
// type相同 可以复用
let props = element.props;
if (element.type === REACT_FRAGMENT_TYPE) {
diff --git a/packages/react-reconciler/src/completeWork.ts b/packages/react-reconciler/src/completeWork.ts
index d609422..7865468 100644
--- a/packages/react-reconciler/src/completeWork.ts
+++ b/packages/react-reconciler/src/completeWork.ts
@@ -1,6 +1,6 @@
import { updateFiberProps } from 'react-dom/src/SyntheticEvent';
import { FiberNode } from './fiber';
-import { NoFlags, Ref, Update } from './fiberFlags';
+import { NoFlags, Ref, Update, Visibility } from './fiberFlags';
import {
appendInitialChild,
createInstance,
@@ -14,6 +14,7 @@ import {
HostRoot,
HostText,
LazyComponent,
+ OffscreenComponent,
SuspenseComponent
} from './workTags';
import { RetryQueue } from './fiberThrow';
@@ -130,6 +131,12 @@ export const completeWork = (workInProgress: FiberNode) => {
}
bubbleProperties(workInProgress);
return null;
+ case OffscreenComponent:
+ const nextIsHidden = workInProgress.memoizedProps?.mode === 'hidden';
+ if (!nextIsHidden) {
+ bubbleProperties(workInProgress);
+ }
+ return null;
default:
console.error('completeWork未定义的fiber.tag', workInProgress);
return null;
diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts
index a2908cd..c15c85b 100644
--- a/packages/react-reconciler/src/fiber.ts
+++ b/packages/react-reconciler/src/fiber.ts
@@ -9,10 +9,16 @@ import {
HostComponent,
WorkTag,
LazyComponent,
- SuspenseComponent
+ SuspenseComponent,
+ OffscreenComponent
} from './workTags';
import { CallbackNode } from 'scheduler';
import { REACT_LAZY_TYPE, REACT_SUSPENSE_TYPE } from 'shared/ReactSymbols';
+import {
+ OffscreenInstance,
+ OffscreenProps,
+ OffscreenVisible
+} from './fiberOffscreenComponent';
export class FiberNode {
pendingProps: Props;
@@ -193,3 +199,19 @@ export function resolveLazyComponentTag(Component: Function): WorkTag {
}
throw '未知的tag';
}
+
+export function createFiberFromOffscreen(
+ pendingProps: OffscreenProps,
+ lanes: Lanes,
+ key: null | string
+) {
+ const fiber = new FiberNode(OffscreenComponent, pendingProps, key);
+ fiber.lanes = lanes;
+ // TODO
+ const primaryChildInstance: OffscreenInstance = {
+ visibility: OffscreenVisible,
+ retryCache: null
+ };
+ fiber.stateNode = primaryChildInstance;
+ return fiber;
+}
diff --git a/packages/react-reconciler/src/fiberFlags.ts b/packages/react-reconciler/src/fiberFlags.ts
index 4d97764..4f2a605 100644
--- a/packages/react-reconciler/src/fiberFlags.ts
+++ b/packages/react-reconciler/src/fiberFlags.ts
@@ -4,6 +4,7 @@ export const NoFlags = 0b00000000000000000000000000;
export const Placement = 0b00000000000000000000000010;
export const Update = 0b00000000000000000000000100;
export const ChildDeletion = 0b00000000000000000000010000;
+export const Visibility = 0b0000000000000010000000000000;
export const DidCapture = 0b0000000000000000000010000000;
diff --git a/packages/react-reconciler/src/fiberOffscreenComponent.ts b/packages/react-reconciler/src/fiberOffscreenComponent.ts
new file mode 100644
index 0000000..01652d2
--- /dev/null
+++ b/packages/react-reconciler/src/fiberOffscreenComponent.ts
@@ -0,0 +1,15 @@
+import { OffscreenMode, Wakeable } from 'shared/ReactTypes';
+import { FiberNode } from './fiber';
+
+export interface OffscreenProps {
+ mode?: OffscreenMode;
+ children?: FiberNode;
+}
+export type OffscreenInstance = {
+ visibility: OffscreenVisibility;
+ retryCache: WeakSet | Set | null;
+};
+
+export type OffscreenVisibility = number;
+
+export const OffscreenVisible = 0b01;
diff --git a/packages/react-reconciler/src/workTags.ts b/packages/react-reconciler/src/workTags.ts
index bc9a774..93d4dab 100644
--- a/packages/react-reconciler/src/workTags.ts
+++ b/packages/react-reconciler/src/workTags.ts
@@ -5,7 +5,8 @@ export type WorkTag =
| typeof HostText
| typeof Fragment
| typeof SuspenseComponent
- | typeof LazyComponent;
+ | typeof LazyComponent
+ | typeof OffscreenComponent;
export const FunctionComponent = 0;
export const HostRoot = 3;
@@ -14,3 +15,15 @@ export const HostText = 6;
export const Fragment = 7;
export const SuspenseComponent = 13;
export const LazyComponent = 16;
+export const OffscreenComponent = 22;
+
+// "ReferenceError: Cannot access 'current2' before initialization
+// at completeWork (http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/completeWork.ts?t=1687782804329:88:7)
+// at completeUnitOfWork (http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/workLoop.ts?t=1687782804329:288:18)
+// at performUnitOfWork (http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/workLoop.ts?t=1687782804329:280:5)
+// at workLoopSync (http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/workLoop.ts?t=1687782804329:268:5)
+// at renderRoot (http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/workLoop.ts?t=1687782804329:163:48)
+// at performSyncWorkOnRoot (http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/workLoop.ts?t=1687782804329:185:22)
+// at http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/syncTaskQueue.ts:14:39
+// at Array.forEach ()
+// at flushSyncCallbacks (http://localhost:5173/@fs/Users/bytedance/projects/big-react/packages/react-reconciler/src/syncTaskQueue.ts:14:17)"
\ No newline at end of file
diff --git a/packages/shared/ReactSymbols.ts b/packages/shared/ReactSymbols.ts
index 10434e4..d29966e 100644
--- a/packages/shared/ReactSymbols.ts
+++ b/packages/shared/ReactSymbols.ts
@@ -12,6 +12,10 @@ export const REACT_LAZY_TYPE = supportSymbol
? Symbol.for('react.lazy')
: 0xead4;
-export const REACT_SUSPENSE_TYPE: symbol = supportSymbol
+export const REACT_SUSPENSE_TYPE = supportSymbol
? Symbol.for('react.suspense')
: 0xead1;
+
+export const REACT_OFFSCREEN_TYPE = supportSymbol
+ ? Symbol.for('react.offscreen')
+ : 0xeae2;
diff --git a/packages/shared/ReactTypes.ts b/packages/shared/ReactTypes.ts
index 864cd74..dcb922f 100644
--- a/packages/shared/ReactTypes.ts
+++ b/packages/shared/ReactTypes.ts
@@ -58,3 +58,5 @@ export type Thenable =
| PendingThenable
| FulfilledThenable
| RejectedThenable;
+
+export type OffscreenMode = 'hidden' | 'visible';