diff --git a/README.md b/README.md
index 774c215..8098776 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,8 @@
- 💪 功能完备,当前可跑通官方测试用例数量:34
- 🚶 按`Git Tag`划分迭代步骤,记录从 0 实现的每个功能
-如果想加入项目对应的`源码交流群`,和 7000+小伙伴们一起交流`React`,可以加我微信,备注「开发」:
+如果想跟着我学习「如何从0到1实现React18」,可以[点击这里](https://qux.xet.tech/s/2wiFh1)
-
## TODO List
diff --git a/demos/fragment/index.html b/demos/fragment/index.html
new file mode 100644
index 0000000..e14a680
--- /dev/null
+++ b/demos/fragment/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ v11测试并发更新
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/fragment/main.tsx b/demos/fragment/main.tsx
new file mode 100644
index 0000000..b793fed
--- /dev/null
+++ b/demos/fragment/main.tsx
@@ -0,0 +1,23 @@
+import { useState, useEffect } from 'react';
+import { createRoot } from 'react-dom/client';
+
+function App() {
+ const [num, update] = useState(0);
+ function onClick() {
+ update(num + 1);
+ }
+
+ const arr =
+ num % 2 === 0
+ ? [a, b, d]
+ : [d, c, b];
+
+ return (
+
+ );
+}
+
+createRoot(document.getElementById('root') as HTMLElement).render();
diff --git a/demos/fragment/vite-env.d.ts b/demos/fragment/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/demos/fragment/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/demos/noop-renderer/index.html b/demos/noop-renderer/index.html
new file mode 100644
index 0000000..5d0dbe9
--- /dev/null
+++ b/demos/noop-renderer/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ noop-renderer测试
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/noop-renderer/main.tsx b/demos/noop-renderer/main.tsx
new file mode 100644
index 0000000..dac0cc7
--- /dev/null
+++ b/demos/noop-renderer/main.tsx
@@ -0,0 +1,21 @@
+import { useState, useEffect } from 'react';
+import * as ReactNoop from 'react-noop-renderer';
+
+const root = ReactNoop.createRoot();
+
+function Parent() {
+ return (
+ <>
+
+ hello world
+ >
+ );
+}
+
+function Child() {
+ return 'Child';
+}
+
+root.render();
+
+window.root = root;
diff --git a/demos/noop-renderer/vite-env.d.ts b/demos/noop-renderer/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/demos/noop-renderer/vite-env.d.ts
@@ -0,0 +1 @@
+///
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/v11/main.tsx b/demos/v11/main.tsx
index 70bfed0..e588260 100644
--- a/demos/v11/main.tsx
+++ b/demos/v11/main.tsx
@@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client';
function App() {
const [num, updateNum] = useState(0);
- const len = 1000;
+ const len = 8;
console.log('num', num);
return (
diff --git a/demos/vite.config.js b/demos/vite.config.js
index 5505acf..8ceff5b 100644
--- a/demos/vite.config.js
+++ b/demos/vite.config.js
@@ -22,10 +22,19 @@ export default defineConfig({
find: 'react-dom',
replacement: path.resolve(__dirname, '../packages/react-dom')
},
+ {
+ find: 'react-reconciler',
+ replacement: path.resolve(__dirname, '../packages/react-reconciler')
+ },
+ {
+ find: 'react-noop-renderer',
+ replacement: path.resolve(__dirname, '../packages/react-noop-renderer')
+ },
{
find: 'hostConfig',
replacement: path.resolve(
__dirname,
+ // '../packages/react-noop-renderer/src/hostConfig.ts'
'../packages/react-dom/src/hostConfig.ts'
)
}
diff --git a/package.json b/package.json
index b3a62f2..c2c2a1b 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,9 @@
"license": "MIT",
"scripts": {
"build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
- "demo": "vite serve demos/v11 --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"
+ "test": "jest --config scripts/jest/jest.config.js"
},
"devDependencies": {
"@babel/core": "^7.18.6",
diff --git a/packages/react-dom/src/SyntheticEvent.ts b/packages/react-dom/src/SyntheticEvent.ts
index 40292b3..9cede2d 100644
--- a/packages/react-dom/src/SyntheticEvent.ts
+++ b/packages/react-dom/src/SyntheticEvent.ts
@@ -9,7 +9,7 @@ const { unstable_runWithPriority: runWithPriority } = Scheduler;
// 支持的事件类型
const validEventTypeList = ['click'];
-export const elementEventPropsKey = '__props';
+export const elementPropsKey = '__props';
type EventCallback = (e: SyntheticEvent) => void;
interface Paths {
@@ -17,20 +17,19 @@ interface Paths {
bubble: EventCallback[];
}
interface SyntheticEvent extends Event {
- type: string;
__stopPropagation: boolean;
}
-export interface PackagedElement extends Element {
- [elementEventPropsKey]: {
- [eventType: string]: EventCallback;
+export interface DOMElement extends Element {
+ [elementPropsKey]: {
+ [key: string]: any;
};
}
function createSyntheticEvent(e: Event): SyntheticEvent {
const syntheticEvent = e as SyntheticEvent;
syntheticEvent.__stopPropagation = false;
- const originStopPropagation = e.stopPropagation;
+ const originStopPropagation = e.stopPropagation.bind(e);
syntheticEvent.stopPropagation = () => {
syntheticEvent.__stopPropagation = true;
@@ -51,27 +50,8 @@ function getEventCallbackNameFromtEventType(
}
// 将支持的事件回调保存在DOM中
-export const updateFiberProps = (
- node: Element,
- props: any
-): PackagedElement => {
- (node as PackagedElement)[elementEventPropsKey] =
- (node as PackagedElement)[elementEventPropsKey] || {};
-
- validEventTypeList.forEach((eventType) => {
- const callbackNameList = getEventCallbackNameFromtEventType(eventType);
-
- if (!callbackNameList) {
- return;
- }
- callbackNameList.forEach((callbackName) => {
- if (Object.hasOwnProperty.call(props, callbackName)) {
- (node as PackagedElement)[elementEventPropsKey][callbackName] =
- props[callbackName];
- }
- });
- });
- return node as PackagedElement;
+export const updateFiberProps = (node: DOMElement, props: any) => {
+ (node as DOMElement)[elementPropsKey] = props;
};
const triggerEventFlow = (paths: EventCallback[], se: SyntheticEvent) => {
@@ -96,7 +76,7 @@ const dispatchEvent = (container: Container, eventType: string, e: Event) => {
}
const { capture, bubble } = collectPaths(
- targetElement as PackagedElement,
+ targetElement as DOMElement,
container,
eventType
);
@@ -115,7 +95,7 @@ const dispatchEvent = (container: Container, eventType: string, e: Event) => {
// 收集从目标元素到HostRoot之间所有目标回调函数
const collectPaths = (
- targetElement: PackagedElement,
+ targetElement: DOMElement,
container: Container,
eventType: string
): Paths => {
@@ -125,12 +105,12 @@ const collectPaths = (
};
// 收集事件回调是冒泡的顺序
while (targetElement && targetElement !== container) {
- const eventProps = targetElement[elementEventPropsKey];
- if (eventProps) {
+ const elementProps = targetElement[elementPropsKey];
+ if (elementProps) {
const callbackNameList = getEventCallbackNameFromtEventType(eventType);
if (callbackNameList) {
callbackNameList.forEach((callbackName, i) => {
- const eventCallback = eventProps[callbackName];
+ const eventCallback = elementProps[callbackName];
if (eventCallback) {
if (i === 0) {
// 反向插入捕获阶段的事件回调
@@ -143,7 +123,7 @@ const collectPaths = (
});
}
}
- targetElement = targetElement.parentNode as PackagedElement;
+ targetElement = targetElement.parentNode as DOMElement;
}
return paths;
};
diff --git a/packages/react-dom/src/hostConfig.ts b/packages/react-dom/src/hostConfig.ts
index ff64362..7bc6466 100644
--- a/packages/react-dom/src/hostConfig.ts
+++ b/packages/react-dom/src/hostConfig.ts
@@ -1,14 +1,15 @@
-import { PackagedElement, updateFiberProps } from './SyntheticEvent';
+import { DOMElement, updateFiberProps } from './SyntheticEvent';
import { FiberNode } from 'react-reconciler/src/fiber';
import { HostText } from 'react-reconciler/src/workTags';
-export type Container = PackagedElement;
-export type Instance = PackagedElement;
+export type Container = Element;
+export type Instance = DOMElement;
export type TextInstance = Text;
export const createInstance = (type: string, props: any): Instance => {
- const element = document.createElement(type);
- return updateFiberProps(element, props);
+ const element = document.createElement(type) as unknown;
+ updateFiberProps(element as DOMElement, props);
+ return element as DOMElement;
};
export const createTextInstance = (content: string) => {
diff --git a/packages/react-dom/src/root.ts b/packages/react-dom/src/root.ts
index 80ab3f2..7b89630 100644
--- a/packages/react-dom/src/root.ts
+++ b/packages/react-dom/src/root.ts
@@ -4,7 +4,7 @@ import {
createContainer
} from 'react-reconciler/src/fiberReconciler';
import { ReactElement } from 'shared/ReactTypes';
-import { initEvent, elementEventPropsKey } from './SyntheticEvent';
+import { initEvent, elementPropsKey } from './SyntheticEvent';
const containerToRoot = new Map();
@@ -14,7 +14,7 @@ function clearContainerDOM(container: Container) {
}
for (let i = 0; i < container.childNodes.length; i++) {
const childNode = container.childNodes[i];
- if (!Object.hasOwnProperty.call(childNode, elementEventPropsKey)) {
+ if (!Object.hasOwnProperty.call(childNode, elementPropsKey)) {
container.removeChild(childNode);
// 当移除节点时,再遍历时length会减少,所以相应i需要减少一个
i--;
diff --git a/packages/react-noop-renderer/src/ReactNoop.ts b/packages/react-noop-renderer/src/ReactNoop.ts
index d48222f..6f0a2f4 100644
--- a/packages/react-noop-renderer/src/ReactNoop.ts
+++ b/packages/react-noop-renderer/src/ReactNoop.ts
@@ -1,5 +1,5 @@
import { ReactElement } from 'shared/ReactTypes';
-import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols';
+import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
import Reconciler from 'react-reconciler';
import * as Scheduler from 'scheduler';
import { Container, Instance } from './hostConfig';
@@ -9,35 +9,34 @@ let idCounter = 0;
export function createRoot() {
const container: Container = {
rootID: idCounter++,
- pendingChildren: [],
children: []
};
const root = Reconciler.createContainer(container);
- function getChildren(root: Container) {
- if (root) {
- return root.children;
+ function getChildren(parent: Container | Instance) {
+ if (parent) {
+ return parent.children;
}
return null;
}
function getChildrenAsJSX(root: Container) {
const children = childToJSX(getChildren(root));
- if (children === null) {
- return null;
- }
if (Array.isArray(children)) {
- // 对应混合了Instance与TextInstance,应该用Fragment处理
- console.error('TODO Fragment的case,还未实现');
+ return {
+ $$typeof: REACT_ELEMENT_TYPE,
+ type: REACT_FRAGMENT_TYPE,
+ key: null,
+ ref: null,
+ props: { children },
+ __mark: 'KaSong'
+ };
}
return children;
}
// 递归将整棵子树变为JSX
function childToJSX(child: any): any {
- if (child === null) {
- return null;
- }
if (['string', 'number'].includes(typeof child)) {
return child;
}
@@ -58,7 +57,6 @@ export function createRoot() {
}
// 这是Instance
if (Array.isArray(child.children)) {
- // This is an instance.
const instance: Instance = child;
const children = childToJSX(instance.children);
const props = instance.props;
@@ -71,7 +69,7 @@ export function createRoot() {
type: instance.type,
key: null,
ref: null,
- props: props,
+ props,
__mark: 'KaSong'
};
}
diff --git a/packages/react-noop-renderer/src/hostConfig.ts b/packages/react-noop-renderer/src/hostConfig.ts
index 754cf2f..9c22e36 100644
--- a/packages/react-noop-renderer/src/hostConfig.ts
+++ b/packages/react-noop-renderer/src/hostConfig.ts
@@ -1,6 +1,5 @@
export interface Container {
rootID: number;
- pendingChildren: (Instance | TextInstance)[];
children: (Instance | TextInstance)[];
}
export interface Instance {
@@ -17,7 +16,6 @@ export interface TextInstance {
}
import { FiberNode } from 'react-reconciler/src/fiber';
-import { DefaultLane } from 'react-reconciler/src/fiberLanes';
import { HostText } from 'react-reconciler/src/workTags';
let instanceCounter = 0;
@@ -25,7 +23,7 @@ let instanceCounter = 0;
export const createInstance = (type: string, props: any): Instance => {
const instance = {
id: instanceCounter++,
- type: type,
+ type,
children: [],
parent: -1,
props
diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts
index e923b17..e8a67d8 100644
--- a/packages/react-reconciler/src/beginWork.ts
+++ b/packages/react-reconciler/src/beginWork.ts
@@ -1,3 +1,4 @@
+import { Fragment } from 'react-reconciler/src/workTags';
import { ReactElement } from 'shared/ReactTypes';
import { mountChildFibers, reconcileChildFibers } from './childFiber';
import { FiberNode } from './fiber';
@@ -10,8 +11,9 @@ import {
HostRoot,
HostText
} from './workTags';
+import { Ref } from './fiberFlags';
-export const beginWork = (workInProgress: FiberNode, renderLane: Lane) => {
+export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
if (__LOG__) {
console.log('beginWork流程', workInProgress.type);
}
@@ -20,47 +22,63 @@ export const beginWork = (workInProgress: FiberNode, renderLane: Lane) => {
switch (workInProgress.tag) {
case HostRoot:
- return updateHostRoot(workInProgress, renderLane);
+ return updateHostRoot(workInProgress, renderLanes);
case HostComponent:
- return updateHostComponent(workInProgress);
+ return updateHostComponent(workInProgress, renderLanes);
case HostText:
return null;
case FunctionComponent:
- return updateFunctionComponent(workInProgress, renderLane);
+ return updateFunctionComponent(workInProgress, renderLanes);
+ case Fragment:
+ return updateFragment(workInProgress, renderLanes);
default:
console.error('beginWork未处理的情况');
return null;
}
};
-function updateFunctionComponent(workInProgress: FiberNode, renderLane: Lane) {
- const nextChildren = renderWithHooks(workInProgress, renderLane);
- reconcileChildren(workInProgress, nextChildren);
+function updateFragment(workInProgress: FiberNode, renderLanes: Lanes) {
+ const nextChildren = workInProgress.pendingProps;
+ reconcileChildren(workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
-function updateHostComponent(workInProgress: FiberNode) {
+function updateFunctionComponent(
+ workInProgress: FiberNode,
+ renderLanes: Lanes
+) {
+ const nextChildren = renderWithHooks(workInProgress, renderLanes);
+ reconcileChildren(workInProgress, nextChildren, renderLanes);
+ return workInProgress.child;
+}
+
+function updateHostComponent(workInProgress: FiberNode, renderLanes: Lanes) {
// 根据element创建fiberNode
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
- reconcileChildren(workInProgress, nextChildren);
+ markRef(workInProgress.alternate, workInProgress);
+ reconcileChildren(workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateHostRoot(workInProgress: FiberNode, renderLanes: Lanes) {
const baseState = workInProgress.memoizedState;
- const updateQueue = workInProgress.updateQueue as UpdateQueue;
+ const updateQueue = workInProgress.updateQueue as UpdateQueue;
const pending = updateQueue.shared.pending;
updateQueue.shared.pending = null;
const { memoizedState } = processUpdateQueue(baseState, pending, renderLanes);
workInProgress.memoizedState = memoizedState;
const nextChildren = workInProgress.memoizedState;
- reconcileChildren(workInProgress, nextChildren);
+ reconcileChildren(workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
-function reconcileChildren(workInProgress: FiberNode, children?: ReactElement) {
+function reconcileChildren(
+ workInProgress: FiberNode,
+ children: any,
+ renderLanes: Lanes
+) {
const current = workInProgress.alternate;
if (current !== null) {
@@ -68,10 +86,27 @@ function reconcileChildren(workInProgress: FiberNode, children?: ReactElement) {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
- children
+ children,
+ renderLanes
);
} else {
// mount
- workInProgress.child = mountChildFibers(workInProgress, null, children);
+ workInProgress.child = mountChildFibers(
+ workInProgress,
+ null,
+ children,
+ renderLanes
+ );
+ }
+}
+
+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/childFiber.ts b/packages/react-reconciler/src/childFiber.ts
index 92d3f3d..4933e83 100644
--- a/packages/react-reconciler/src/childFiber.ts
+++ b/packages/react-reconciler/src/childFiber.ts
@@ -1,12 +1,14 @@
-import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols';
+import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
import { Props, ReactElement } from 'shared/ReactTypes';
import {
createFiberFromElement,
+ createFiberFromFragment,
createWorkInProgress,
FiberNode
} from './fiber';
import { ChildDeletion, Placement } from './fiberFlags';
-import { HostText } from './workTags';
+import { Lanes } from './fiberLanes';
+import { Fragment, HostText } from './workTags';
/**
* mount/reconcile只负责 Placement(插入)/Placement(移动)/ChildDeletion(删除)
@@ -33,19 +35,19 @@ function ChildReconciler(shouldTrackEffects: boolean) {
currentFirstChild: FiberNode | null
) {
if (!shouldTrackEffects) {
- return null;
+ return;
}
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
- return null;
}
function reconcileSingleElement(
returnFiber: FiberNode,
currentFirstChild: FiberNode | null,
- element: ReactElement
+ element: ReactElement,
+ lanes: Lanes
) {
// 前:abc 后:a 删除bc
// 前:a 后:b 删除b、创建a
@@ -60,7 +62,11 @@ function ChildReconciler(shouldTrackEffects: boolean) {
if (element.$$typeof === REACT_ELEMENT_TYPE) {
if (current.type === element.type) {
// type相同 可以复用
- const existing = useFiber(current, element.props);
+ let props = element.props;
+ if (element.type === REACT_FRAGMENT_TYPE) {
+ props = element.props.children as Props;
+ }
+ const existing = useFiber(current, props);
existing.return = returnFiber;
// 当前节点可复用,其他兄弟节点都删除
deleteRemainingChildren(returnFiber, current.sibling);
@@ -80,7 +86,12 @@ function ChildReconciler(shouldTrackEffects: boolean) {
}
}
// 创建新的
- const fiber = createFiberFromElement(element);
+ let fiber;
+ if (element.type === REACT_FRAGMENT_TYPE) {
+ fiber = createFiberFromFragment(element.props.children, lanes, key);
+ } else {
+ fiber = createFiberFromElement(element, lanes);
+ }
fiber.return = returnFiber;
return fiber;
}
@@ -96,64 +107,96 @@ function ChildReconciler(shouldTrackEffects: boolean) {
returnFiber: FiberNode,
existingChildren: ExistingChildren,
index: number,
- element: ReactElement | string | number | null
+ element: any,
+ lanes: Lanes
): FiberNode | null {
- let keyToUse;
- if (
- element === null ||
- typeof element === 'string' ||
- typeof element === 'number'
- ) {
- keyToUse = index;
- } else {
- keyToUse = element.key !== null ? element.key : index;
- }
+ // 确定key
+ const keyToUse = element.key !== null ? element.key : index;
+
const before = existingChildren.get(keyToUse);
- if (
- element === null ||
- typeof element === 'string' ||
- typeof element === 'number'
- ) {
+ // 处理文本节点
+ if (typeof element === 'string' || typeof element === 'number') {
if (before) {
// fiber key相同,如果type也相同,则可复用
- existingChildren.delete(keyToUse);
if (before.tag === HostText) {
// 复用文本节点
+ existingChildren.delete(keyToUse);
return useFiber(before, { content: element + '' });
- } else {
- deleteChild(returnFiber, before);
}
}
-
- // 新建文本节点
- return element === null
- ? null
- : new FiberNode(HostText, { content: element }, null);
+ return new FiberNode(HostText, { content: element }, null);
}
+ // 处理ReactElement
if (typeof element === 'object' && element !== null) {
switch (element.$$typeof) {
case REACT_ELEMENT_TYPE:
+ if (element.type === REACT_FRAGMENT_TYPE) {
+ return updateFragment(
+ returnFiber,
+ before,
+ element,
+ lanes,
+ keyToUse,
+ existingChildren
+ );
+ }
if (before) {
// fiber key相同,如果type也相同,则可复用
- existingChildren.delete(keyToUse);
if (before.type === element.type) {
- // 复用
+ existingChildren.delete(keyToUse);
return useFiber(before, element.props);
- } else {
- deleteChild(returnFiber, before);
}
}
- return createFiberFromElement(element);
+ return createFiberFromElement(element, lanes);
+ }
+ // 处理Fragment
+ /**
+ * after可能还是array 考虑如下,其中list是个array:
+ *
+ * 这种情况我们应该视after为Fragment
+ */
+ if (Array.isArray(element)) {
+ return updateFragment(
+ returnFiber,
+ before,
+ element,
+ lanes,
+ keyToUse,
+ existingChildren
+ );
}
}
return null;
}
+ function updateFragment(
+ returnFiber: FiberNode,
+ current: FiberNode | undefined,
+ elements: any[],
+ lanes: Lanes,
+ key: string,
+ existingChildren: ExistingChildren
+ ): FiberNode {
+ let fiber;
+ if (!current || current.tag !== Fragment) {
+ fiber = createFiberFromFragment(elements, lanes, key);
+ } else {
+ existingChildren.delete(key);
+ fiber = useFiber(current, elements);
+ }
+ fiber.return = returnFiber;
+ return fiber;
+ }
+
function reconcileSingleTextNode(
returnFiber: FiberNode,
currentFirstChild: FiberNode | null,
- content: string
+ content: string,
+ lanes: Lanes
) {
// 前:b 后:a
// TODO 前:abc 后:a
@@ -173,6 +216,7 @@ function ChildReconciler(shouldTrackEffects: boolean) {
}
const created = new FiberNode(HostText, { content }, null);
+ created.lanes = lanes;
created.return = returnFiber;
return created;
}
@@ -180,7 +224,8 @@ function ChildReconciler(shouldTrackEffects: boolean) {
function reconcileChildrenArray(
returnFiber: FiberNode,
currentFirstChild: FiberNode | null,
- newChild: (ReactElement | string)[]
+ newChild: any[],
+ lanes: Lanes
) {
// 遍历到的最后一个可复用fiber在before中的index
let lastPlacedIndex = 0;
@@ -200,26 +245,15 @@ function ChildReconciler(shouldTrackEffects: boolean) {
// 遍历流程
for (let i = 0; i < newChild.length; i++) {
- /**
- * TODO after可能还是array 考虑如下,其中list是个array:
- *
- * 这种情况我们应该视after为Fragment
- */
const after = newChild[i];
- if (Array.isArray(after)) {
- console.error('TODO 还未实现嵌套Array情况下的diff');
- }
-
// after对应的fiber,可能来自于复用,也可能是新建
const newFiber = updateFromMap(
returnFiber,
existingChildren,
i,
- after
+ after,
+ lanes
) as FiberNode;
/**
@@ -272,30 +306,59 @@ function ChildReconciler(shouldTrackEffects: boolean) {
function reconcileChildFibers(
returnFiber: FiberNode,
currentFirstChild: FiberNode | null,
- newChild?: ReactElement
+ newChild: any,
+ lanes: Lanes
): FiberNode | null {
+ // 对于类似 这样内部直接使用<>作为Fragment的情况
+ const isUnkeyedTopLevelFragment =
+ typeof newChild === 'object' &&
+ newChild !== null &&
+ newChild.type === REACT_FRAGMENT_TYPE &&
+ newChild.key === null;
+ if (isUnkeyedTopLevelFragment) {
+ newChild = newChild.props.children;
+ }
+
// newChild 为 JSX
// currentFirstChild 为 fiberNode
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
- reconcileSingleElement(returnFiber, currentFirstChild, newChild)
+ reconcileSingleElement(
+ returnFiber,
+ currentFirstChild,
+ newChild,
+ lanes
+ )
);
}
+ // 第一层数组直接遍历,嵌套数组作为Fragment处理
+ // 如:
if (Array.isArray(newChild)) {
- return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
+ return reconcileChildrenArray(
+ returnFiber,
+ currentFirstChild,
+ newChild,
+ lanes
+ );
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
- reconcileSingleTextNode(returnFiber, currentFirstChild, newChild + '')
+ reconcileSingleTextNode(
+ returnFiber,
+ currentFirstChild,
+ newChild + '',
+ lanes
+ )
);
}
// 其他情况全部视为删除旧的节点
- return deleteRemainingChildren(returnFiber, currentFirstChild);
+ deleteRemainingChildren(returnFiber, currentFirstChild);
+ return null;
}
return reconcileChildFibers;
diff --git a/packages/react-reconciler/src/commitWork.ts b/packages/react-reconciler/src/commitWork.ts
index 7875ae9..1185c71 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';
@@ -23,48 +25,49 @@ import {
FunctionComponent,
HostComponent,
HostRoot,
- HostText
+ HostText,
+ Fragment
} from './workTags';
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 +93,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
@@ -214,6 +268,30 @@ function getHostParent(fiber: FiberNode) {
console.error('getHostParent未找到hostParent');
}
+function isHostTypeFiberNode(fiber: FiberNode) {
+ const tag = fiber.tag;
+ return [HostComponent, HostRoot, HostText].includes(tag);
+}
+
+function recordHostChildrenToDelete(beginNode: FiberNode): FiberNode[] {
+ if (isHostTypeFiberNode(beginNode)) return [beginNode];
+ const hostChildrenToDelete: FiberNode[] = [];
+ const processQueue: FiberNode[] = [beginNode];
+ while (processQueue.length) {
+ const node = processQueue.shift();
+ if (node && isHostTypeFiberNode(node)) {
+ hostChildrenToDelete.push(node);
+ continue;
+ }
+ let childNode = node?.child;
+ while (childNode) {
+ processQueue.push(childNode);
+ childNode = childNode.sibling;
+ }
+ }
+ return hostChildrenToDelete;
+}
+
/**
* 删除需要考虑:
* HostComponent:需要遍历他的子树,为后续解绑ref创造条件,HostComponent本身只需删除最上层节点即可
@@ -223,20 +301,17 @@ function commitDeletion(childToDelete: FiberNode, root: FiberRootNode) {
if (__LOG__) {
console.log('删除DOM、组件unmount', childToDelete);
}
- let firstHostFiber: FiberNode | null = null;
+ // 在Fragment之前,只需删除子树的根Host节点,但支持Fragment后,可能需要删除同级多个节点
+ const hostChildrenToDelete: FiberNode[] =
+ recordHostChildrenToDelete(childToDelete);
commitNestedUnmounts(childToDelete, (unmountFiber) => {
switch (unmountFiber.tag) {
case HostComponent:
- if (firstHostFiber === null) {
- firstHostFiber = unmountFiber;
- }
// 解绑ref
+ safelyDetachRef(unmountFiber);
return;
case HostText:
- if (firstHostFiber === null) {
- firstHostFiber = unmountFiber;
- }
return;
case FunctionComponent:
// effect相关操作
@@ -245,9 +320,11 @@ function commitDeletion(childToDelete: FiberNode, root: FiberRootNode) {
}
});
- if (firstHostFiber !== null) {
+ if (hostChildrenToDelete.length) {
const hostParent = getHostParent(childToDelete) as Container;
- removeChild((firstHostFiber as FiberNode).stateNode, hostParent);
+ hostChildrenToDelete.forEach((hostChild) => {
+ removeChild(hostChild.stateNode, hostParent);
+ });
}
childToDelete.return = null;
diff --git a/packages/react-reconciler/src/completeWork.ts b/packages/react-reconciler/src/completeWork.ts
index 49e61a6..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,
@@ -8,12 +8,17 @@ import {
Instance
} from 'hostConfig';
import {
+ Fragment,
FunctionComponent,
HostComponent,
HostRoot,
HostText
} from './workTags';
+function markRef(fiber: FiberNode) {
+ fiber.flags |= Ref;
+}
+
const appendAllChildren = (parent: Instance, workInProgress: FiberNode) => {
// 遍历workInProgress所有子孙 DOM元素,依次挂载
let node = workInProgress.child;
@@ -73,19 +78,28 @@ 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
bubbleProperties(workInProgress);
return null;
+ case FunctionComponent:
case HostRoot:
+ case Fragment:
bubbleProperties(workInProgress);
return null;
case HostText:
@@ -105,9 +119,6 @@ export const completeWork = (workInProgress: FiberNode) => {
// 冒泡flag
bubbleProperties(workInProgress);
return null;
- case FunctionComponent:
- 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 4d819e6..23b35d3 100644
--- a/packages/react-reconciler/src/fiber.ts
+++ b/packages/react-reconciler/src/fiber.ts
@@ -3,7 +3,12 @@ import { Flags, NoFlags } from './fiberFlags';
import { Effect } from './fiberHooks';
import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes';
import { Container } from 'hostConfig';
-import { FunctionComponent, HostComponent, WorkTag } from './workTags';
+import {
+ Fragment,
+ FunctionComponent,
+ HostComponent,
+ WorkTag
+} from './workTags';
import { CallbackNode } from 'scheduler';
export class FiberNode {
@@ -33,7 +38,7 @@ export class FiberNode {
constructor(tag: WorkTag, pendingProps: Props, key: Key) {
// 实例
this.tag = tag;
- this.key = key;
+ this.key = key || null;
this.stateNode = null;
this.type = null;
@@ -103,8 +108,11 @@ export class FiberRootNode {
}
}
-export function createFiberFromElement(element: ReactElement): FiberNode {
- const { type, key, props } = element;
+export function createFiberFromElement(
+ element: ReactElement,
+ lanes: Lanes
+): FiberNode {
+ const { type, key, props, ref } = element;
let fiberTag: WorkTag = FunctionComponent;
if (typeof type === 'string') {
@@ -114,10 +122,22 @@ export function createFiberFromElement(element: ReactElement): FiberNode {
}
const fiber = new FiberNode(fiberTag, props, key);
fiber.type = type;
+ fiber.lanes = lanes;
+ fiber.ref = ref;
return fiber;
}
+export function createFiberFromFragment(
+ elements: ReactElement[],
+ lanes: Lanes,
+ key: Key
+): FiberNode {
+ const fiber = new FiberNode(Fragment, elements, key);
+ fiber.lanes = lanes;
+ return fiber;
+}
+
export const createWorkInProgress = (
current: FiberNode,
pendingProps: Props
@@ -147,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 c1552e3..9952414 100644
--- a/packages/react-reconciler/src/fiberHooks.ts
+++ b/packages/react-reconciler/src/fiberHooks.ts
@@ -1,4 +1,4 @@
-import { Dispatcher, Disptach } from 'react/src/currentDispatcher';
+import { Dispatcher, Dispatch } from 'react/src/currentDispatcher';
import { Action } from 'shared/ReactTypes';
import sharedInternals from 'shared/internals';
import { FiberNode } from './fiber';
@@ -68,17 +68,19 @@ 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(
initialState: (() => State) | State
-): [State, Disptach] {
+): [State, Dispatch] {
const hook = mountWorkInProgressHook();
let memoizedState: State;
if (initialState instanceof Function) {
@@ -100,7 +102,7 @@ function mountState(
return [memoizedState, dispatch];
}
-function updateState(): [State, Disptach] {
+function updateState(): [State, Dispatch] {
const hook = updateWorkInProgressHook();
const queue = hook.updateQueue as UpdateQueue;
const baseState = hook.baseState;
@@ -150,7 +152,7 @@ function updateState(): [State, Disptach] {
hook.baseQueue = newBaseQueue;
}
- return [hook.memoizedState, queue.dispatch as Disptach];
+ return [hook.memoizedState, queue.dispatch as Dispatch];
}
function dispatchSetState(
@@ -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/updateQueue.ts b/packages/react-reconciler/src/updateQueue.ts
index 0b61c09..39dc416 100644
--- a/packages/react-reconciler/src/updateQueue.ts
+++ b/packages/react-reconciler/src/updateQueue.ts
@@ -1,4 +1,4 @@
-import { Disptach } from 'react/src/currentDispatcher';
+import { Dispatch } from 'react/src/currentDispatcher';
import { Action } from 'shared/ReactTypes';
import { Update } from './fiberFlags';
import {
@@ -19,7 +19,7 @@ export interface UpdateQueue {
shared: {
pending: Update | null;
};
- dispatch: Disptach | null;
+ dispatch: Dispatch | null;
}
// 创建
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-reconciler/src/workTags.ts b/packages/react-reconciler/src/workTags.ts
index f2ea036..63a63f9 100644
--- a/packages/react-reconciler/src/workTags.ts
+++ b/packages/react-reconciler/src/workTags.ts
@@ -2,9 +2,11 @@ export type WorkTag =
| typeof FunctionComponent
| typeof HostRoot
| typeof HostComponent
- | typeof HostText;
+ | typeof HostText
+ | typeof Fragment;
export const FunctionComponent = 0;
export const HostRoot = 3;
export const HostComponent = 5;
export const HostText = 6;
+export const Fragment = 7;
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/jsx-dev-runtime.ts b/packages/react/jsx-dev-runtime.ts
index bd931d7..c7fa533 100644
--- a/packages/react/jsx-dev-runtime.ts
+++ b/packages/react/jsx-dev-runtime.ts
@@ -2,4 +2,4 @@
* 这个文件是为了方便demos下的示例调试用的
*/
-export { jsxDEV } from './src/jsx';
+export { jsxDEV, Fragment } from './src/jsx';
diff --git a/packages/react/src/currentDispatcher.ts b/packages/react/src/currentDispatcher.ts
index 3672736..0fc2ef9 100644
--- a/packages/react/src/currentDispatcher.ts
+++ b/packages/react/src/currentDispatcher.ts
@@ -1,11 +1,12 @@
import { Action } from 'shared/ReactTypes';
export type Dispatcher = {
- useState: (initialState: (() => T) | T) => [T, Disptach];
+ useState: (initialState: (() => T) | T) => [T, Dispatch];
useEffect: (callback: (() => void) | void, deps: any[] | void) => void;
+ useRef: (initialValue: T) => { current: T };
};
-export type Disptach = (action: Action) => void;
+export type Dispatch = (action: Action) => void;
const currentDispatcher: { current: null | Dispatcher } = {
current: null
diff --git a/packages/react/src/jsx.ts b/packages/react/src/jsx.ts
index 887b6a8..72abc65 100644
--- a/packages/react/src/jsx.ts
+++ b/packages/react/src/jsx.ts
@@ -1,4 +1,4 @@
-import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols';
+import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
import { Key, ElementType, Ref, Props, ReactElement } from 'shared/ReactTypes';
const ReactElement = function (
@@ -27,6 +27,8 @@ function hasValidRef(config: any) {
return config.ref !== undefined;
}
+export const Fragment = REACT_FRAGMENT_TYPE;
+
export const jsx = (type: ElementType, config: any, ...maybeChildren: any) => {
let key: Key = null;
const props: any = {};
diff --git a/packages/shared/ReactSymbols.ts b/packages/shared/ReactSymbols.ts
index f87da96..7608cf6 100644
--- a/packages/shared/ReactSymbols.ts
+++ b/packages/shared/ReactSymbols.ts
@@ -3,3 +3,7 @@ const supportSymbol = typeof Symbol === 'function' && Symbol.for;
export const REACT_ELEMENT_TYPE = supportSymbol
? Symbol.for('react.element')
: 0xeac7;
+
+export const REACT_FRAGMENT_TYPE = supportSymbol
+ ? Symbol.for('react.fragment')
+ : 0xeacb;
diff --git a/packages/shared/ReactTypes.ts b/packages/shared/ReactTypes.ts
index 2eb6a75..f45e4dc 100644
--- a/packages/shared/ReactTypes.ts
+++ b/packages/shared/ReactTypes.ts
@@ -1,9 +1,9 @@
-export type Ref = any;
+export type Ref = { current: any } | ((instance: any) => void);
export type ElementType = any;
export type Key = string | null;
export type Props = {
[key: string]: any;
- children?: ReactElement;
+ children?: any;
};
export interface ReactElement {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b7ddff9..ff2d4af 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -84,9 +84,11 @@ importers:
packages/react-dom:
specifiers:
react-reconciler: workspace:*
+ scheduler: ^0.23.0
shared: workspace:*
dependencies:
react-reconciler: link:../react-reconciler
+ scheduler: 0.23.0
shared: link:../shared
packages/react-noop-renderer:
diff --git a/jest.config.js b/scripts/jest/jest.config.js
similarity index 95%
rename from jest.config.js
rename to scripts/jest/jest.config.js
index 695a412..b7b6829 100644
--- a/jest.config.js
+++ b/scripts/jest/jest.config.js
@@ -2,6 +2,7 @@ const { defaults } = require('jest-config');
module.exports = {
...defaults,
+ rootDir: process.cwd(),
modulePathIgnorePatterns: ['/.history'],
moduleDirectories: [
// 对于 React ReactDOM
diff --git a/tsconfig.json b/tsconfig.json
index 0ea5b22..fa64390 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,8 +12,8 @@
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
"noImplicitReturns": false,
"skipLibCheck": true,
"typeRoots": ["./types", "./node_modules/@types/"],