├── .commitlintrc.js
├── .eslintrc.json
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierrc.json
├── README.md
├── babel.config.js
├── demos
└── test-fc
│ ├── index.html
│ └── main.tsx
├── package.json
├── packages
├── react-dom
│ ├── client.ts
│ ├── index.ts
│ ├── package.json
│ ├── src
│ │ ├── SyntheticEvent.ts
│ │ ├── hostConfig.ts
│ │ └── root.ts
│ └── test-utils.ts
├── react-noop-renderer
│ ├── index.ts
│ ├── package.json
│ └── src
│ │ ├── hostConfig.ts
│ │ └── root.ts
├── react-reconciler
│ ├── package.json
│ └── src
│ │ ├── __tests__
│ │ └── ReactEffectOrdering-test.js
│ │ ├── beginWork.ts
│ │ ├── childFiber.ts
│ │ ├── commitWork.ts
│ │ ├── completeWork.ts
│ │ ├── fiber.ts
│ │ ├── fiberFlags.ts
│ │ ├── fiberHooks.ts
│ │ ├── fiberLanes.ts
│ │ ├── fiberReconciler.ts
│ │ ├── hookEffectTags.ts
│ │ ├── reconciler.d.ts
│ │ ├── syncTaskQueue.ts
│ │ ├── updateQueue.ts
│ │ ├── workLoop.ts
│ │ └── workTags.ts
├── react
│ ├── __tests__
│ │ └── ReactElement-test.js
│ ├── index.ts
│ ├── jsx-dev-runtime.ts
│ ├── package.json
│ └── src
│ │ ├── currentDispatcher.ts
│ │ └── jsx.ts
└── shared
│ ├── ReactSymbols.ts
│ ├── ReactTypes.ts
│ ├── internals.ts
│ └── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
├── jest
│ ├── jest.config.js
│ ├── reactTestMatchers.js
│ ├── schedulerTestMatchers.js
│ └── setupJest.js
├── rollup
│ ├── dev.config.js
│ ├── react-dom.config.js
│ ├── react-noop-renderer.config.js
│ ├── react.config.js
│ └── utils.js
└── vite
│ └── vite.config.js
└── tsconfig.json
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional']
3 | };
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "node": true,
6 | "jest": true
7 | },
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "prettier",
12 | "plugin:prettier/recommended"
13 | ],
14 | "parser": "@typescript-eslint/parser",
15 | "parserOptions": {
16 | "ecmaVersion": "latest",
17 | "sourceType": "module"
18 | },
19 | "plugins": ["@typescript-eslint", "prettier"],
20 | "rules": {
21 | "prettier/prettier": "error",
22 | "no-case-declarations": "off",
23 | "no-constant-condition": "off",
24 | "@typescript-eslint/ban-ts-comment": "off",
25 | "@typescript-eslint/no-unused-vars": "off",
26 | "@typescript-eslint/no-var-requires": "off",
27 | "@typescript-eslint/no-explicit-any": "off",
28 | "no-unused-vars": "off"
29 | }
30 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint -e
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm lint
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": true,
5 | "singleQuote": true,
6 | "semi": true,
7 | "trailingComma": "none",
8 | "bracketSpacing": true
9 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 《手写 React 源码》
5 | 深入理解 React 源码,手把手带你构建自己的 React 库。
6 |
7 | ---
8 |
9 | ### 目录
10 |
11 | - [1. 项目框架搭建](https://2xiao.github.io/my-react/1)
12 | - [2. 实现 JSX](https://2xiao.github.io/my-react/2)
13 | - [3. 实现 Reconciler](https://2xiao.github.io/my-react/3)
14 | - [4. 实现更新机制](https://2xiao.github.io/my-react/4)
15 | - [5. 实现 Render 阶段](https://2xiao.github.io/my-react/5)
16 | - [6. 实现 Commit 阶段](https://2xiao.github.io/my-react/6)
17 | - [7. 实现 ReactDOM](https://2xiao.github.io/my-react/7)
18 | - [8. 实现 useState](https://2xiao.github.io/my-react/8)
19 | - [9. 接入测试框架](https://2xiao.github.io/my-react/9)
20 | - [10. 实现单节点 update](https://2xiao.github.io/my-react/10)
21 | - [11. 实现事件系统](https://2xiao.github.io/my-react/11)
22 | - [12. 实现 Diff 算法](https://2xiao.github.io/my-react/12)
23 | - [13. 实现 Fragment](https://2xiao.github.io/my-react/13)
24 | - [14. 实现同步调度流程](https://2xiao.github.io/my-react/14)
25 | - [15. 实现 useEffect](https://2xiao.github.io/my-react/15)
26 | - [16. 实现 Noop Renderer](https://2xiao.github.io/my-react/16)
27 |
28 | ---
29 |
30 | ### 代码
31 |
32 | 教程地址:[https://2xiao.github.io/my-react](https://2xiao.github.io/my-react)
33 |
34 | 源代码地址:[https://github.com/2xiao/my-react](https://github.com/2xiao/my-react)
35 |
36 | 使用 Git Tag 划分迭代步骤,手把手带你实现 React v18 的核心功能。
37 |
38 | 欢迎「Star ⭐️ 」 和 「Fork」,这是对我最大的鼓励和支持。
39 |
40 | ---
41 |
42 | ### 特色
43 |
44 | - 教程详细,带你构建自己的 React 库;
45 |
46 | - 功能全面,可跑通官方测试用例数量:34;
47 |
48 | - 按 Git Tag 划分迭代步骤,记录每个功能的实现过程;
49 |
50 | ---
51 |
52 | ### 你将收获
53 |
54 | React 是由卓越工程师们在数年时间内精心打造的库,其中必定蕴含了许多值得借鉴的经验和智慧。
55 |
56 | 如果你渴望更进一步,不仅仅停留在 API 的使用层面,而是追求更深入前端技术的探索,那么掌握 React 源码将成为你技能提升的极佳途径。
57 |
58 | 本书遵循 React 源码的核心思想,通俗易懂的解析 React 源码,带你从零实现 React v18 的核心功能,学完本书,你将有这些收获:
59 |
60 | - **面试加分**:框架底层原理是面试必问环节,熟悉 React 源码会为你的面试加分,也会为你拿下 offer 增加不少筹码;
61 | - **提升开发效率**:熟悉 React 源码之后,会对 React 的运行流程有新的认识,让你在日常的开发中,对性能优化、使用技巧和 bug 解决更加得心应手;
62 | - **巩固基础知识**:学习本书也顺便巩固了数据结构和算法,如 reconciler 中使用了 fiber、update、链表等数据结构,diff 算法要考虑怎样降低对比复杂度;
63 |
64 | ---
65 |
66 | ### 版权声明
67 |
68 | 本书是基于 [《从 0 实现 React18》](https://qux.xet.tech/s/2wiFh1) 整理创作的,视频教程的作者是 [@卡颂](https://github.com/BetaSu/) 。
69 |
70 | 本作品采用 知识署名-非商业性使用-禁止演绎 (BY-NC-ND) 4.0 国际许可 [协议](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.zh-Hans) 进行许可。
71 |
72 | 只要保持原作者署名和非商用,您可以自由地阅读、分享、修改本书。
73 |
74 | [开始阅读 ->](https://2xiao.github.io/my-react/1)
75 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env'],
3 | plugins: [
4 | [
5 | '@babel/plugin-transform-react-jsx',
6 | { throwIfNamespace: false }
7 | ]
8 | ]
9 | };
--------------------------------------------------------------------------------
/demos/test-fc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demos/test-fc/main.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | const jsx = (
5 |
6 | hello my-react
7 |
8 | );
9 |
10 | function App() {
11 | const [num, update] = useState(100)
12 | return ( update(50)}>
13 | {new Array(num).fill(0).map((_, i) => {
14 | return {i}
15 | })}
16 |
17 | );
18 | }
19 |
20 | function Child({children}) {
21 | const now = performance.now()
22 | while(performance.now() - now < 4) {}
23 |
24 | return {children};
25 | }
26 |
27 | const root = ReactDOM.createRoot(document.getElementById('root'));
28 | root.render();
29 |
30 | // root.render(jsx);
31 |
32 | window.root = root;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages",
8 | "build-dev": "rimraf dist && rollup --config scripts/rollup/dev.config.js --bundleConfigAsCjs",
9 | "demo": "vite serve demos/test-fc --config scripts/vite/vite.config.js --force",
10 | "test": "jest --config scripts/jest/jest.config.js"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "@babel/core": "^7.24.0",
17 | "@babel/plugin-transform-react-jsx": "^7.23.4",
18 | "@babel/preset-env": "^7.24.0",
19 | "@commitlint/cli": "^18.0.0",
20 | "@commitlint/config-conventional": "^18.0.0",
21 | "@rollup/plugin-alias": "^5.1.0",
22 | "@rollup/plugin-commonjs": "^25.0.7",
23 | "@rollup/plugin-replace": "^5.0.5",
24 | "@types/react": "^18.2.56",
25 | "@types/react-dom": "^18.2.19",
26 | "@types/scheduler": "^0.16.8",
27 | "@typescript-eslint/eslint-plugin": "^6.8.0",
28 | "@typescript-eslint/parser": "^6.8.0",
29 | "@vitejs/plugin-react": "^4.2.1",
30 | "commitlint": "^18.0.0",
31 | "eslint": "^8.52.0",
32 | "eslint-config-prettier": "^9.0.0",
33 | "eslint-plugin-prettier": "^5.0.1",
34 | "eslint-plugin-react-hooks": "^4.6.0",
35 | "eslint-plugin-react-refresh": "^0.4.5",
36 | "husky": "^8.0.3",
37 | "jest": "^29.7.0",
38 | "jest-config": "^29.7.0",
39 | "jest-environment-jsdom": "^29.7.0",
40 | "jest-react": "^0.14.0",
41 | "prettier": "^3.0.3",
42 | "rimraf": "^5.0.5",
43 | "rollup": "^4.1.4",
44 | "rollup-plugin-generate-package-json": "^3.2.0",
45 | "rollup-plugin-typescript2": "^0.36.0",
46 | "typescript": "^5.2.2",
47 | "vite": "^5.1.4"
48 | },
49 | "dependencies": {
50 | "scheduler": "^0.23.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/react-dom/client.ts:
--------------------------------------------------------------------------------
1 | import * as ReactDOM from './src/root';
2 |
3 | export const createRoot = ReactDOM.createRoot;
4 | export default ReactDOM;
5 |
--------------------------------------------------------------------------------
/packages/react-dom/index.ts:
--------------------------------------------------------------------------------
1 | import * as ReactDOM from './src/root';
2 |
3 | export const createRoot = ReactDOM.createRoot;
4 | export default ReactDOM;
5 |
--------------------------------------------------------------------------------
/packages/react-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dom",
3 | "version": "1.0.0",
4 | "description": "",
5 | "module": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace: *",
8 | "react-reconciler": "workspace: *"
9 | },
10 | "peerDependencies": {
11 | "react": "workspace: *"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC"
16 | }
17 |
--------------------------------------------------------------------------------
/packages/react-dom/src/SyntheticEvent.ts:
--------------------------------------------------------------------------------
1 | import { Container } from 'hostConfig';
2 | import {
3 | unstable_ImmediatePriority,
4 | unstable_NormalPriority,
5 | unstable_UserBlockingPriority,
6 | unstable_runWithPriority
7 | } from 'scheduler';
8 | import { Props } from 'shared/ReactTypes';
9 |
10 | // 支持的事件类型
11 | const validEventTypeList = ['click'];
12 |
13 | type EventCallback = (e: Event) => void;
14 |
15 | interface Paths {
16 | capture: EventCallback[];
17 | bubble: EventCallback[];
18 | }
19 |
20 | interface SyntheticEvent extends Event {
21 | __stopPropagation: boolean;
22 | }
23 |
24 | export const elementPropsKey = '__props';
25 |
26 | export interface DOMElement extends Element {
27 | [elementPropsKey]: Props;
28 | }
29 |
30 | // 将事件的回调保存在 DOM 中
31 | export function updateFiberProps(node: DOMElement, props: Props) {
32 | node[elementPropsKey] = props;
33 | }
34 |
35 | export function initEvent(container: Container, eventType: string) {
36 | if (!validEventTypeList.includes(eventType)) {
37 | console.warn('initEvent 未实现的事件类型', eventType);
38 | return;
39 | }
40 | if (__DEV__) {
41 | console.log('初始化事件', eventType);
42 | }
43 | container.addEventListener(eventType, (e: Event) => {
44 | dispatchEvent(container, eventType, e);
45 | });
46 | }
47 |
48 | function dispatchEvent(container: Container, eventType: string, e: Event) {
49 | const targetElement = e.target;
50 | if (targetElement == null) {
51 | console.warn('事件不存在targetElement', e);
52 | return;
53 | }
54 | // 收集沿途事件
55 | const { bubble, capture } = collectPaths(
56 | targetElement as DOMElement,
57 | container,
58 | eventType
59 | );
60 |
61 | // 构造合成事件
62 | const syntheticEvent = createSyntheticEvent(e);
63 |
64 | // 遍历捕获 capture
65 | triggerEventFlow(capture, syntheticEvent);
66 |
67 | // 遍历冒泡 bubble
68 | if (!syntheticEvent.__stopPropagation) {
69 | triggerEventFlow(bubble, syntheticEvent);
70 | }
71 | }
72 |
73 | function collectPaths(
74 | targetElement: DOMElement,
75 | container: Container,
76 | eventType: string
77 | ) {
78 | const paths: Paths = {
79 | capture: [],
80 | bubble: []
81 | };
82 |
83 | // 收集
84 | while (targetElement && targetElement !== container) {
85 | const elementProps = targetElement[elementPropsKey];
86 | if (elementProps) {
87 | const callbackNameList = getEventCallbackNameFromEventType(eventType);
88 | if (callbackNameList) {
89 | callbackNameList.forEach((callbackName, i) => {
90 | const callback = elementProps[callbackName];
91 | if (callback) {
92 | if (i == 0) {
93 | paths.capture.unshift(callback);
94 | } else {
95 | paths.bubble.push(callback);
96 | }
97 | }
98 | });
99 | }
100 | }
101 | targetElement = targetElement.parentNode as DOMElement;
102 | }
103 |
104 | return paths;
105 | }
106 |
107 | function getEventCallbackNameFromEventType(
108 | eventType: string
109 | ): string[] | undefined {
110 | return {
111 | click: ['onClickCapture', 'onClick']
112 | }[eventType];
113 | }
114 |
115 | function createSyntheticEvent(e: Event) {
116 | const syntheticEvent = e as SyntheticEvent;
117 | syntheticEvent.__stopPropagation = false;
118 | const originStopPropagation = e.stopPropagation;
119 |
120 | syntheticEvent.stopPropagation = () => {
121 | syntheticEvent.__stopPropagation = true;
122 | if (originStopPropagation) {
123 | originStopPropagation();
124 | }
125 | };
126 |
127 | return syntheticEvent;
128 | }
129 |
130 | function triggerEventFlow(
131 | paths: EventCallback[],
132 | syntheticEvent: SyntheticEvent
133 | ) {
134 | for (let i = 0; i < paths.length; i++) {
135 | const callback = paths[i];
136 | // 使用调度器,根据优先级执行 syntheticEvent
137 | unstable_runWithPriority(
138 | eventTypeToSchedulerPriority(syntheticEvent.type),
139 | () => {
140 | callback.call(null, syntheticEvent);
141 | }
142 | );
143 |
144 | if (syntheticEvent.__stopPropagation) {
145 | break;
146 | }
147 | }
148 | }
149 |
150 | function eventTypeToSchedulerPriority(eventType: string) {
151 | switch (eventType) {
152 | case 'click':
153 | case 'keydown':
154 | case 'keyup':
155 | return unstable_ImmediatePriority;
156 | case 'scroll':
157 | return unstable_UserBlockingPriority;
158 | default:
159 | return unstable_NormalPriority;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/packages/react-dom/src/hostConfig.ts:
--------------------------------------------------------------------------------
1 | import { FiberNode } from 'react-reconciler/src/fiber';
2 | import { HostComponent, HostText } from 'react-reconciler/src/workTags';
3 | import { updateFiberProps, DOMElement } from './SyntheticEvent';
4 |
5 | export type Container = Element;
6 | export type Instance = Element;
7 | export type TextInstance = Text;
8 |
9 | export const createInstance = (type: string, porps: any): Instance => {
10 | const element = document.createElement(type) as unknown;
11 | updateFiberProps(element as DOMElement, porps);
12 | return element as DOMElement;
13 | };
14 |
15 | export const appendInitialChild = (
16 | parent: Instance | Container,
17 | child: Instance
18 | ) => {
19 | parent.appendChild(child);
20 | };
21 |
22 | export const createTextInstance = (content: string) => {
23 | const element = document.createTextNode(content);
24 | return element;
25 | };
26 |
27 | export const appendChildToContainer = (
28 | child: Instance,
29 | parent: Instance | Container
30 | ) => {
31 | parent.appendChild(child);
32 | };
33 |
34 | export const insertChildToContainer = (
35 | child: Instance,
36 | container: Container,
37 | before: Instance
38 | ) => {
39 | container.insertBefore(child, before);
40 | };
41 |
42 | export const commitUpdate = (fiber: FiberNode) => {
43 | if (__DEV__) {
44 | console.log('执行 Update 操作', fiber);
45 | }
46 | switch (fiber.tag) {
47 | case HostComponent:
48 | return updateFiberProps(fiber.stateNode, fiber.memoizedProps);
49 | case HostText:
50 | const text = fiber.memoizedProps.content;
51 | return commitTextUpdate(fiber.stateNode, text);
52 | default:
53 | if (__DEV__) {
54 | console.warn('未实现的 commitUpdate 类型', fiber);
55 | }
56 | break;
57 | }
58 | };
59 |
60 | export const commitTextUpdate = (
61 | textInstance: TextInstance,
62 | content: string
63 | ) => {
64 | textInstance.textContent = content;
65 | };
66 |
67 | export const removeChild = (
68 | child: Instance | TextInstance,
69 | container: Container
70 | ) => {
71 | container.removeChild(child);
72 | };
73 |
74 | export const scheduleMicroTask =
75 | typeof queueMicrotask === 'function'
76 | ? queueMicrotask
77 | : typeof Promise === 'function'
78 | ? (callback: (...args: any) => void) => Promise.resolve(null).then(callback)
79 | : setTimeout;
80 |
--------------------------------------------------------------------------------
/packages/react-dom/src/root.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createContainer,
3 | updateContainer
4 | } from 'react-reconciler/src/fiberReconciler';
5 | import { Container } from './hostConfig';
6 | import { ReactElementType } from 'shared/ReactTypes';
7 | import { initEvent } from './SyntheticEvent';
8 |
9 | // ReactDOM.createRoot(root).render();
10 | export function createRoot(container: Container) {
11 | const root = createContainer(container);
12 |
13 | return {
14 | render(element: ReactElementType) {
15 | initEvent(container, 'click');
16 | return updateContainer(element, root);
17 | }
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/packages/react-dom/test-utils.ts:
--------------------------------------------------------------------------------
1 | import { ReactElementType } from 'shared/ReactTypes';
2 | import { createRoot } from 'react-dom/client';
3 |
4 | export function renderIntoDocument(element: ReactElementType) {
5 | const div = document.createElement('div');
6 | return createRoot(div).render(element);
7 | }
8 |
--------------------------------------------------------------------------------
/packages/react-noop-renderer/index.ts:
--------------------------------------------------------------------------------
1 | import * as ReactNoopRenderer from './src/root';
2 |
3 | export default ReactNoopRenderer;
4 |
--------------------------------------------------------------------------------
/packages/react-noop-renderer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-noop-renderer",
3 | "version": "1.0.0",
4 | "description": "",
5 | "module": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace:*",
8 | "react-reconciler": "workspace:*"
9 | },
10 | "peerDependencies": {
11 | "react": "workspace:*"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC"
16 | }
--------------------------------------------------------------------------------
/packages/react-noop-renderer/src/hostConfig.ts:
--------------------------------------------------------------------------------
1 | import { FiberNode } from 'react-reconciler/src/fiber';
2 | import { HostText } from 'react-reconciler/src/workTags';
3 | import { Props } from 'shared/ReactTypes';
4 |
5 | export interface Container {
6 | rootID: number;
7 | children: (Instance | TextInstance)[];
8 | }
9 |
10 | export interface Instance {
11 | id: number;
12 | type: string;
13 | children: (Instance | TextInstance)[];
14 | parent: number;
15 | props: Props;
16 | }
17 |
18 | export interface TextInstance {
19 | text: string;
20 | id: number;
21 | parent: number;
22 | }
23 |
24 | let instanceCounter = 0;
25 |
26 | export const createInstance = (type: string, props: Props): Instance => {
27 | const instance = {
28 | id: instanceCounter++,
29 | type,
30 | children: [],
31 | parent: -1,
32 | props
33 | };
34 | return instance;
35 | };
36 |
37 | export const appendInitialChild = (
38 | parent: Instance | Container,
39 | child: Instance
40 | ) => {
41 | const prevParentID = child.parent;
42 | const parentID = 'rootID' in parent ? parent.rootID : parent.id;
43 |
44 | if (prevParentID !== -1 && prevParentID !== parentID) {
45 | throw new Error('不能重复挂载child');
46 | }
47 | child.parent = parentID;
48 | parent.children.push(child);
49 | };
50 |
51 | export const createTextInstance = (content: string) => {
52 | const instance = {
53 | text: content,
54 | id: instanceCounter++,
55 | parent: -1
56 | };
57 | return instance;
58 | };
59 |
60 | export const appendChildToContainer = (child: Instance, parent: Container) => {
61 | const prevParentID = child.parent;
62 |
63 | if (prevParentID !== -1 && prevParentID !== parent.rootID) {
64 | throw new Error('不能重复挂载child');
65 | }
66 | child.parent = parent.rootID;
67 | parent.children.push(child);
68 | };
69 |
70 | export function insertChildToContainer(
71 | child: Instance,
72 | container: Container,
73 | before: Instance
74 | ) {
75 | const beforeIndex = container.children.indexOf(before);
76 | if (beforeIndex === -1) {
77 | throw new Error('before节点不存在');
78 | }
79 | const index = container.children.indexOf(child);
80 | if (index !== -1) {
81 | container.children.splice(index, 1);
82 | }
83 | container.children.splice(beforeIndex, 0, child);
84 | }
85 |
86 | export function commitUpdate(fiber: FiberNode) {
87 | switch (fiber.tag) {
88 | case HostText:
89 | const text = fiber.memoizedProps?.content;
90 | return commitTextUpdate(fiber.stateNode, text);
91 | default:
92 | if (__DEV__) {
93 | console.warn('未实现的 commitUpdate 类型', fiber);
94 | }
95 | break;
96 | }
97 | }
98 |
99 | export const commitTextUpdate = (
100 | textInstance: TextInstance,
101 | content: string
102 | ) => {
103 | textInstance.text = content;
104 | };
105 |
106 | export const removeChild = (
107 | child: Instance | TextInstance,
108 | container: Container
109 | ) => {
110 | const index = container.children.indexOf(child);
111 | if (index === -1) {
112 | throw new Error('child not found');
113 | }
114 | container.children.splice(index, 1);
115 | };
116 |
117 | export const scheduleMicroTask =
118 | typeof queueMicrotask === 'function'
119 | ? queueMicrotask
120 | : typeof Promise === 'function'
121 | ? (callback: (...args: any) => void) => Promise.resolve(null).then(callback)
122 | : setTimeout;
123 |
--------------------------------------------------------------------------------
/packages/react-noop-renderer/src/root.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createContainer,
3 | updateContainer
4 | } from 'react-reconciler/src/fiberReconciler';
5 | import { Container, Instance } from './hostConfig';
6 | import { ReactElementType } from 'shared/ReactTypes';
7 | import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
8 | import * as Scheduler from 'scheduler';
9 |
10 | let idCounter = 0;
11 | export function createRoot() {
12 | const container: Container = {
13 | rootID: idCounter++,
14 | children: []
15 | };
16 | // @ts-ignore
17 | const root = createContainer(container);
18 |
19 | function getChildren(parent: Container | Instance) {
20 | if (parent) {
21 | return parent.children;
22 | }
23 | return null;
24 | }
25 |
26 | function getChildrenAsJSX(root: Container) {
27 | const children = childToJSX(getChildren(root));
28 | if (Array.isArray(children)) {
29 | return {
30 | $$typeof: REACT_ELEMENT_TYPE,
31 | type: REACT_FRAGMENT_TYPE,
32 | key: null,
33 | ref: null,
34 | props: { children },
35 | __mark: 'erxiao'
36 | };
37 | }
38 | return children;
39 | }
40 |
41 | function childToJSX(child: any): any {
42 | // 文本节点
43 | if (typeof child === 'string' || typeof child === 'number') {
44 | return child;
45 | }
46 |
47 | // 数组
48 | if (Array.isArray(child)) {
49 | if (child.length === 0) return null;
50 | if (child.length === 1) return childToJSX(child[0]);
51 | const children = child.map(childToJSX);
52 |
53 | if (
54 | children.every(
55 | (child) => typeof child === 'string' || typeof child === 'number'
56 | )
57 | ) {
58 | return children.join('');
59 | }
60 | return children;
61 | }
62 |
63 | // Instance
64 | if (Array.isArray(child.children)) {
65 | const instance: Instance = child;
66 | const children = childToJSX(instance.children);
67 | const props = instance.props;
68 |
69 | if (children !== null) {
70 | props.children = children;
71 | }
72 |
73 | return {
74 | $$typeof: REACT_ELEMENT_TYPE,
75 | type: instance.type,
76 | key: null,
77 | ref: null,
78 | props,
79 | __mark: 'erxiao'
80 | };
81 | }
82 |
83 | // TextInstance
84 | return child.text;
85 | }
86 |
87 | return {
88 | _Scheduler: Scheduler,
89 | render(element: ReactElementType) {
90 | return updateContainer(element, root);
91 | },
92 | getChildren() {
93 | return getChildren(container);
94 | },
95 | getChildrenAsJSX() {
96 | return getChildrenAsJSX(container);
97 | }
98 | };
99 | }
100 |
--------------------------------------------------------------------------------
/packages/react-reconciler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-reconciler",
3 | "version": "1.0.0",
4 | "description": "React package for creating custom renderers.",
5 | "module": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace: *"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "author": "",
13 | "license": "ISC"
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/__tests__/ReactEffectOrdering-test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | * @emails react-core
8 | * @jest-environment node
9 | */
10 |
11 | /* eslint-disable no-func-assign */
12 |
13 | 'use strict';
14 |
15 | let React;
16 | let ReactNoop;
17 | let Scheduler;
18 | let act;
19 | let useEffect;
20 |
21 | describe('ReactHooksWithNoopRenderer', () => {
22 | beforeEach(() => {
23 | jest.resetModules();
24 | jest.useFakeTimers();
25 |
26 | React = require('react');
27 | act = require('jest-react').act;
28 | Scheduler = require('scheduler');
29 | ReactNoop = require('react-noop-renderer');
30 |
31 | useEffect = React.useEffect;
32 | });
33 |
34 | test('passive unmounts on deletion are fired in parent -> child order', async () => {
35 | const root = ReactNoop.createRoot();
36 |
37 | function Parent() {
38 | useEffect(() => {
39 | return () => Scheduler.unstable_yieldValue('Unmount parent');
40 | });
41 | return ;
42 | }
43 |
44 | function Child() {
45 | useEffect(() => {
46 | return () => Scheduler.unstable_yieldValue('Unmount child');
47 | });
48 | return 'Child';
49 | }
50 |
51 | await act(async () => {
52 | root.render();
53 | });
54 |
55 | expect(root).toMatchRenderedOutput('Child');
56 | await act(async () => {
57 | root.render(null);
58 | });
59 | expect(Scheduler).toHaveYielded(['Unmount parent', 'Unmount child']);
60 | });
61 | });
--------------------------------------------------------------------------------
/packages/react-reconciler/src/beginWork.ts:
--------------------------------------------------------------------------------
1 | import { ReactElementType } from 'shared/ReactTypes';
2 | import { FiberNode } from './fiber';
3 | import { UpdateQueue, processUpdateQueue } from './updateQueue';
4 | import {
5 | Fragment,
6 | FunctionComponent,
7 | HostComponent,
8 | HostRoot,
9 | HostText
10 | } from './workTags';
11 | import { reconcileChildFibers, mountChildFibers } from './childFiber';
12 | import { renderWithHooks } from './fiberHooks';
13 | import { Lane } from './fiberLanes';
14 |
15 | // 比较并返回子 FiberNode
16 | export const beginWork = (workInProgress: FiberNode, renderLane: Lane) => {
17 | switch (workInProgress.tag) {
18 | case HostRoot:
19 | return updateHostRoot(workInProgress, renderLane);
20 | case HostComponent:
21 | return updateHostComponent(workInProgress);
22 | case FunctionComponent:
23 | return updateFunctionComponent(workInProgress, renderLane);
24 | case HostText:
25 | return updateHostText();
26 | case Fragment:
27 | return updateFragment(workInProgress);
28 | default:
29 | if (__DEV__) {
30 | console.warn('beginWork 未实现的类型', workInProgress.tag);
31 | }
32 | break;
33 | }
34 | };
35 |
36 | function updateHostRoot(workInProgress: FiberNode, renderLane: Lane) {
37 | // 根据当前节点和工作中节点的状态进行比较,处理属性等更新逻辑
38 | const baseState = workInProgress.memoizedState;
39 | const updateQueue = workInProgress.updateQueue as UpdateQueue;
40 | const pending = updateQueue.shared.pending;
41 | // 清空更新链表
42 | updateQueue.shared.pending = null;
43 | // 计算待更新状态的最新值
44 | const { memoizedState } = processUpdateQueue(baseState, pending, renderLane);
45 | workInProgress.memoizedState = memoizedState;
46 |
47 | // 处理子节点的更新逻辑
48 | const nextChildren = workInProgress.memoizedState;
49 | reconcileChildren(workInProgress, nextChildren);
50 |
51 | // 返回新的子节点
52 | return workInProgress.child;
53 | }
54 |
55 | function updateHostComponent(workInProgress: FiberNode) {
56 | const nextProps = workInProgress.pendingProps;
57 | const nextChildren = nextProps.children;
58 | reconcileChildren(workInProgress, nextChildren);
59 | return workInProgress.child;
60 | }
61 |
62 | function updateFunctionComponent(workInProgress: FiberNode, renderLane: Lane) {
63 | const nextChildren = renderWithHooks(workInProgress, renderLane);
64 | reconcileChildren(workInProgress, nextChildren);
65 | return workInProgress.child;
66 | }
67 |
68 | function updateHostText() {
69 | // 没有子节点,直接返回 null
70 | return null;
71 | }
72 |
73 | function updateFragment(workInProgress: FiberNode) {
74 | const nextChildren = workInProgress.pendingProps;
75 | reconcileChildren(workInProgress, nextChildren);
76 | return workInProgress.child;
77 | }
78 |
79 | // 对比子节点的 current FiberNode 与 子节点的 ReactElement
80 | // 生成子节点对应的 workInProgress FiberNode
81 | function reconcileChildren(
82 | workInProgress: FiberNode,
83 | children?: ReactElementType
84 | ) {
85 | // alternate 指向节点的备份节点,即 current
86 | const current = workInProgress.alternate;
87 | if (current !== null) {
88 | // 组件的更新阶段
89 | workInProgress.child = reconcileChildFibers(
90 | workInProgress,
91 | current?.child,
92 | children
93 | );
94 | } else {
95 | // 首屏渲染阶段
96 | workInProgress.child = mountChildFibers(workInProgress, null, children);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/childFiber.ts:
--------------------------------------------------------------------------------
1 | import { Key, Props, ReactElementType } from 'shared/ReactTypes';
2 | import {
3 | FiberNode,
4 | createFiberFromElement,
5 | createFiberFromFragment,
6 | createWorkInProgress
7 | } from './fiber';
8 | import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
9 | import { Fragment, HostText } from './workTags';
10 | import { ChildDeletion, Placement } from './fiberFlags';
11 |
12 | type ExistingChildren = Map;
13 |
14 | function ChildReconciler(shouldTrackSideEffects: boolean) {
15 | // 从父节点中删除指定的子节点
16 | function deleteChild(returnFiber: FiberNode, childToDelete: FiberNode): void {
17 | if (!shouldTrackSideEffects) {
18 | return;
19 | }
20 | const deletions = returnFiber.deletions;
21 | if (deletions === null) {
22 | returnFiber.deletions = [childToDelete];
23 | returnFiber.flags |= ChildDeletion;
24 | } else {
25 | deletions.push(childToDelete);
26 | }
27 | }
28 |
29 | // 删除当前节点的所有兄弟节点
30 | function deleteRemainingChildren(
31 | returnFiber: FiberNode,
32 | currentFirstChild: FiberNode | null
33 | ): void {
34 | if (!shouldTrackSideEffects) return;
35 | let childToDelete = currentFirstChild;
36 | while (childToDelete !== null) {
37 | deleteChild(returnFiber, childToDelete);
38 | childToDelete = childToDelete.sibling;
39 | }
40 | }
41 |
42 | // 复用 Fiber 节点
43 | function useFiber(fiber: FiberNode, pendingProps: Props): FiberNode {
44 | const clone = createWorkInProgress(fiber, pendingProps);
45 | clone.index = 0;
46 | clone.sibling = null;
47 | return clone;
48 | }
49 |
50 | // 处理单个 Element 节点的情况
51 | // 对比 currentFiber 与 ReactElement,生成 workInProgress FiberNode
52 | function reconcileSingleElement(
53 | returnFiber: FiberNode,
54 | currentFiber: FiberNode | null,
55 | element: ReactElementType
56 | ) {
57 | // 组件的更新阶段
58 | while (currentFiber !== null) {
59 | if (currentFiber.key === element.key) {
60 | if (element.$$typeof === REACT_ELEMENT_TYPE) {
61 | if (currentFiber.type === element.type) {
62 | // key 和 type 都相同,当前节点可以复用旧的 Fiber 节点
63 | // 处理 Fragment 的情况
64 | let props: Props = element.props;
65 | if (element.type === REACT_FRAGMENT_TYPE) {
66 | props = element.props.children;
67 | }
68 |
69 | const existing = useFiber(currentFiber, props);
70 | existing.return = returnFiber;
71 | // 剩下的兄弟节点标记删除
72 | deleteRemainingChildren(returnFiber, currentFiber.sibling);
73 | return existing;
74 | }
75 | // key 相同,但 type 不同,删除所有旧的 Fiber 节点
76 | deleteRemainingChildren(returnFiber, currentFiber);
77 | break;
78 | } else {
79 | if (__DEV__) {
80 | console.warn('还未实现的 React 类型', element);
81 | break;
82 | }
83 | }
84 | } else {
85 | // key 不同,删除当前旧的 Fiber 节点,继续遍历兄弟节点
86 | deleteChild(returnFiber, currentFiber);
87 | currentFiber = currentFiber.sibling;
88 | }
89 | }
90 |
91 | // 创建新的 Fiber 节点
92 | let fiber;
93 | if (element.type === REACT_FRAGMENT_TYPE) {
94 | fiber = createFiberFromFragment(element.props.children, element.key);
95 | } else {
96 | fiber = createFiberFromElement(element);
97 | }
98 | fiber.return = returnFiber;
99 | return fiber;
100 | }
101 |
102 | // 处理文本节点的情况
103 | // 对比 currentFiber 与 ReactElement,生成 workInProgress FiberNode
104 | function reconcileSingleTextNode(
105 | returnFiber: FiberNode,
106 | currentFiber: FiberNode | null,
107 | content: string | number
108 | ) {
109 | while (currentFiber !== null) {
110 | // 组件的更新阶段
111 | if (currentFiber.tag === HostText) {
112 | // 复用旧的 Fiber 节点
113 | const existing = useFiber(currentFiber, { content });
114 | existing.return = returnFiber;
115 | deleteRemainingChildren(returnFiber, currentFiber.sibling);
116 | return existing;
117 | } else {
118 | // 删除旧的 Fiber 节点
119 | deleteChild(returnFiber, currentFiber);
120 | currentFiber = currentFiber.sibling;
121 | }
122 | }
123 | // 创建新的 Fiber 节点
124 | const fiber = new FiberNode(HostText, { content }, null);
125 | fiber.return = returnFiber;
126 | return fiber;
127 | }
128 |
129 | // 为 Fiber 节点添加更新 flags
130 | function placeSingleChild(fiber: FiberNode) {
131 | // 首屏渲染且追踪副作用时,才添加更新 flags
132 | if (shouldTrackSideEffects && fiber.alternate == null) {
133 | fiber.flags |= Placement;
134 | }
135 | return fiber;
136 | }
137 |
138 | function reconcileChildrenArray(
139 | returnFiber: FiberNode,
140 | currentFirstChild: FiberNode | null,
141 | newChild: any[]
142 | ) {
143 | // 最后一个可复用 Fiber 在 current 中的 index
144 | let lastPlacedIndex: number = 0;
145 | // 创建的第一个新 Fiber
146 | let firstNewFiber: FiberNode | null = null;
147 | // 创建的最后一个新 Fiber
148 | let lastNewFiber: FiberNode | null = null;
149 |
150 | // 1. 将 current 中所有同级 Fiber 节点保存在 Map 中
151 | const existingChildren: ExistingChildren = new Map();
152 | let current = currentFirstChild;
153 | while (current !== null) {
154 | const keyToUse =
155 | current.key !== null ? current.key : current.index.toString();
156 | existingChildren.set(keyToUse, current);
157 | current = current.sibling;
158 | }
159 |
160 | // 2. 遍历 newChild 数组,判断是否可复用
161 | for (let i = 0; i < newChild.length; i++) {
162 | const after = newChild[i];
163 | const newFiber = updateFromMap(returnFiber, existingChildren, i, after);
164 |
165 | if (newFiber == null) {
166 | continue;
167 | }
168 |
169 | // 3. 标记插入或移动
170 | newFiber.index = i;
171 | newFiber.return = returnFiber;
172 |
173 | if (lastNewFiber == null) {
174 | lastNewFiber = newFiber;
175 | firstNewFiber = newFiber;
176 | } else {
177 | lastNewFiber.sibling = newFiber;
178 | lastNewFiber = lastNewFiber.sibling;
179 | }
180 |
181 | if (!shouldTrackSideEffects) {
182 | continue;
183 | }
184 |
185 | const current = newFiber.alternate;
186 | if (current !== null) {
187 | const oldIndex = current.index;
188 | if (oldIndex < lastPlacedIndex) {
189 | // 标记移动
190 | newFiber.flags |= Placement;
191 | continue;
192 | } else {
193 | // 不移动
194 | lastPlacedIndex = oldIndex;
195 | }
196 | } else {
197 | // 首屏渲染阶段,标记插入
198 | newFiber.flags |= Placement;
199 | }
200 | }
201 |
202 | // 4. 将 Map 中剩下的标记为删除
203 | existingChildren.forEach((fiber) => {
204 | deleteChild(returnFiber, fiber);
205 | });
206 |
207 | return firstNewFiber;
208 | }
209 |
210 | function updateFromMap(
211 | returnFiber: FiberNode,
212 | existingChildren: ExistingChildren,
213 | index: number,
214 | element: any
215 | ): FiberNode | null {
216 | const keyToUse = element.key !== null ? element.key : index.toString();
217 | const before = existingChildren.get(keyToUse);
218 |
219 | // HostText
220 | if (typeof element === 'string' || typeof element === 'number') {
221 | // 可复用,复用旧的 Fiber 节点
222 | if (before && before.tag === HostText) {
223 | existingChildren.delete(keyToUse);
224 | return useFiber(before, { content: element + '' });
225 | }
226 | // 不可复用,创建新的 Fiber 节点
227 | return new FiberNode(HostText, { content: element + '' }, null);
228 | }
229 |
230 | // ReactElement
231 | if (typeof element === 'object' && element !== null) {
232 | switch (element.$$typeof) {
233 | case REACT_ELEMENT_TYPE:
234 | if (element.type === REACT_FRAGMENT_TYPE) {
235 | return updateFragment(
236 | returnFiber,
237 | before,
238 | element,
239 | keyToUse,
240 | existingChildren
241 | );
242 | }
243 | // 可复用,复用旧的 Fiber 节点
244 | if (before && before.type === element.type) {
245 | existingChildren.delete(keyToUse);
246 | return useFiber(before, element.props);
247 | }
248 | // 不可复用,创建新的 Fiber 节点
249 | return createFiberFromElement(element);
250 |
251 | // TODO case REACT_FRAGMENT_TYPE
252 | default:
253 | break;
254 | }
255 | }
256 |
257 | // 数组类型的 ReactElement,如:
258 | if (Array.isArray(element)) {
259 | return updateFragment(
260 | returnFiber,
261 | before,
262 | element,
263 | keyToUse,
264 | existingChildren
265 | );
266 | }
267 |
268 | return null;
269 | }
270 |
271 | // 闭包,根绝 shouldTrackSideEffects 返回不同 reconcileChildFibers 的实现
272 | return function reconcileChildFibers(
273 | returnFiber: FiberNode,
274 | currentFiber: FiberNode | null,
275 | newChild?: any
276 | ) {
277 | // 判断 Fragment
278 | const isUnkeyedTopLevelFragment =
279 | typeof newChild === 'object' &&
280 | newChild !== null &&
281 | newChild.type === REACT_FRAGMENT_TYPE &&
282 | newChild.key === null;
283 | if (isUnkeyedTopLevelFragment) {
284 | newChild = newChild.props.children;
285 | }
286 |
287 | // 判断当前 fiber 的类型
288 | // ReactElement 节点
289 | if (typeof newChild == 'object' && newChild !== null) {
290 | // 处理多个 ReactElement 节点的情况
291 | if (Array.isArray(newChild)) {
292 | return reconcileChildrenArray(returnFiber, currentFiber, newChild);
293 | }
294 |
295 | // 处理单个 ReactElement 节点的情况
296 | switch (newChild.$$typeof) {
297 | case REACT_ELEMENT_TYPE:
298 | return placeSingleChild(
299 | reconcileSingleElement(returnFiber, currentFiber, newChild)
300 | );
301 |
302 | default:
303 | if (__DEV__) {
304 | console.warn('未实现的 reconcile 类型', newChild);
305 | }
306 | break;
307 | }
308 | }
309 |
310 | // 文本节点
311 | if (typeof newChild == 'string' || typeof newChild == 'number') {
312 | return placeSingleChild(
313 | reconcileSingleTextNode(returnFiber, currentFiber, newChild)
314 | );
315 | }
316 |
317 | // default 情况,删除旧的 Fiber 节点
318 | if (currentFiber !== null) {
319 | deleteRemainingChildren(returnFiber, currentFiber);
320 | }
321 |
322 | if (__DEV__) {
323 | console.warn('未实现的 reconcile 类型', newChild);
324 | }
325 | return null;
326 | };
327 |
328 | // 复用或新建 Fragment
329 | function updateFragment(
330 | returnFiber: FiberNode,
331 | current: FiberNode | undefined,
332 | elements: any[],
333 | key: Key,
334 | existingChildren: ExistingChildren
335 | ) {
336 | let fiber;
337 | if (!current || current.tag !== Fragment) {
338 | fiber = createFiberFromFragment(elements, key);
339 | } else {
340 | existingChildren.delete(key);
341 | fiber = useFiber(current, elements);
342 | }
343 | fiber.return = returnFiber;
344 | return fiber;
345 | }
346 | }
347 |
348 | // 组件的更新阶段中,追踪副作用
349 | export const reconcileChildFibers = ChildReconciler(true);
350 |
351 | // 首屏渲染阶段中不追踪副作用,只对根节点执行一次 DOM 插入操作
352 | export const mountChildFibers = ChildReconciler(false);
353 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/commitWork.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Container,
3 | Instance,
4 | appendChildToContainer,
5 | commitUpdate,
6 | insertChildToContainer,
7 | removeChild
8 | } from 'hostConfig';
9 | import { FiberNode, FiberRootNode, PendingPassiveEffects } from './fiber';
10 | import {
11 | ChildDeletion,
12 | MutationMask,
13 | NoFlags,
14 | PassiveEffect,
15 | PassiveMask,
16 | Placement,
17 | Update
18 | } from './fiberFlags';
19 | import {
20 | FunctionComponent,
21 | HostComponent,
22 | HostRoot,
23 | HostText
24 | } from './workTags';
25 | import { Effect, FCUpdateQueue } from './fiberHooks';
26 | import { EffectTags, HookHasEffect } from './hookEffectTags';
27 |
28 | let nextEffect: FiberNode | null = null;
29 |
30 | export const commitMutationEffects = (
31 | finishedWork: FiberNode,
32 | root: FiberRootNode
33 | ) => {
34 | nextEffect = finishedWork;
35 |
36 | // 深度优先遍历 Fiber 树,寻找更新 flags
37 | while (nextEffect !== null) {
38 | // 向下遍历
39 | const child: FiberNode | null = nextEffect.child;
40 | if (
41 | (nextEffect.subtreeFlags & (MutationMask | PassiveMask)) !== NoFlags &&
42 | child !== null
43 | ) {
44 | // 子节点存在 mutation 阶段需要执行的 flags
45 | nextEffect = child;
46 | } else {
47 | // 子节点不存在 mutation 阶段需要执行的 flags 或没有子节点
48 | // 向上遍历
49 | up: while (nextEffect !== null) {
50 | // 处理 flags
51 | commitMutationEffectsOnFiber(nextEffect, root);
52 |
53 | const sibling: FiberNode | null = nextEffect.sibling;
54 | // 遍历兄弟节点
55 | if (sibling !== null) {
56 | nextEffect = sibling;
57 | break up;
58 | }
59 | // 遍历父节点
60 | nextEffect = nextEffect.return;
61 | }
62 | }
63 | }
64 | };
65 |
66 | const commitMutationEffectsOnFiber = (
67 | finishedWork: FiberNode,
68 | root: FiberRootNode
69 | ) => {
70 | const flags = finishedWork.flags;
71 | if ((flags & Placement) !== NoFlags) {
72 | commitPlacement(finishedWork);
73 | // 处理完之后,从 flags 中删除 Placement 标记
74 | finishedWork.flags &= ~Placement;
75 | }
76 | if ((flags & ChildDeletion) !== NoFlags) {
77 | const deletions = finishedWork.deletions;
78 | if (deletions !== null) {
79 | deletions.forEach((childToDelete) => {
80 | commitDeletion(childToDelete, root);
81 | });
82 | }
83 | finishedWork.flags &= ~ChildDeletion;
84 | }
85 | if ((flags & Update) !== NoFlags) {
86 | commitUpdate(finishedWork);
87 | finishedWork.flags &= ~Update;
88 | }
89 | if ((flags & PassiveEffect) !== NoFlags) {
90 | // 收集回调
91 | commitPassiveEffect(finishedWork, root, 'update');
92 | finishedWork.flags &= ~PassiveEffect;
93 | }
94 | };
95 |
96 | // 执行 DOM 插入操作,将 FiberNode 对应的 DOM 插入 parent DOM 中
97 | const commitPlacement = (finishedWork: FiberNode) => {
98 | if (__DEV__) {
99 | console.log('执行 Placement 操作', finishedWork);
100 | }
101 | // parent DOM
102 | const hostParent = getHostParent(finishedWork) as Container;
103 |
104 | // Host sibling
105 | const sibling = getHostSibling(finishedWork);
106 |
107 | appendPlacementNodeIntoContainer(finishedWork, hostParent, sibling);
108 | };
109 |
110 | // 获取兄弟 Host 节点
111 | const getHostSibling = (fiber: FiberNode) => {
112 | let node: FiberNode = fiber;
113 | findSibling: while (true) {
114 | // 没有兄弟节点时,向上遍历
115 | while (node.sibling == null) {
116 | const parent = node.return;
117 | if (
118 | parent == null ||
119 | parent.tag == HostComponent ||
120 | parent.tag == HostRoot
121 | ) {
122 | return null;
123 | }
124 | node = parent;
125 | }
126 |
127 | // 向下遍历
128 | node.sibling.return = node.return;
129 | node = node.sibling;
130 | while (node.tag !== HostText && node.tag !== HostComponent) {
131 | // 不稳定的 Host 节点不能作为目标兄弟 Host 节点
132 | if ((node.flags & Placement) !== NoFlags) {
133 | continue findSibling;
134 | }
135 | if (node.child == null) {
136 | continue findSibling;
137 | } else {
138 | node.child.return = node;
139 | node = node.child;
140 | }
141 | }
142 |
143 | if ((node.flags & Placement) == NoFlags) {
144 | return node.stateNode;
145 | }
146 | }
147 | };
148 |
149 | // 获取 parent DOM
150 | const getHostParent = (fiber: FiberNode) => {
151 | let parent = fiber.return;
152 | while (parent !== null) {
153 | const parentTag = parent.tag;
154 | if (parentTag === HostRoot) {
155 | return (parent.stateNode as FiberRootNode).container;
156 | }
157 | if (parentTag === HostComponent) {
158 | return parent.stateNode as Container;
159 | }
160 | parent = parent.return;
161 | }
162 | if (__DEV__) {
163 | console.warn('未找到 host parent', fiber);
164 | }
165 | };
166 |
167 | const appendPlacementNodeIntoContainer = (
168 | finishedWork: FiberNode,
169 | hostParent: Container,
170 | before?: Instance
171 | ) => {
172 | if (finishedWork.tag === HostComponent || finishedWork.tag === HostText) {
173 | if (before) {
174 | // 执行移动操作
175 | insertChildToContainer(finishedWork.stateNode, hostParent, before);
176 | } else {
177 | // 执行插入操作
178 | appendChildToContainer(finishedWork.stateNode, hostParent);
179 | }
180 | } else {
181 | const child = finishedWork.child;
182 | if (child !== null) {
183 | appendPlacementNodeIntoContainer(child, hostParent);
184 | let sibling = child.sibling;
185 | while (sibling !== null) {
186 | appendPlacementNodeIntoContainer(sibling, hostParent);
187 | sibling = sibling.sibling;
188 | }
189 | }
190 | }
191 | };
192 |
193 | function recordChildrenToDelete(
194 | childrenToDelete: FiberNode[],
195 | unmountFiber: FiberNode
196 | ) {
197 | const lastOne = childrenToDelete[childrenToDelete.length - 1];
198 | if (!lastOne) {
199 | childrenToDelete.push(unmountFiber);
200 | } else {
201 | let node = lastOne.sibling;
202 | while (node !== null) {
203 | if (unmountFiber == node) {
204 | childrenToDelete.push(unmountFiber);
205 | }
206 | node = node.sibling;
207 | }
208 | }
209 | }
210 |
211 | // 删除节点及其子树
212 | const commitDeletion = (childToDelete: FiberNode, root: FiberRootNode) => {
213 | if (__DEV__) {
214 | console.log('执行 Deletion 操作', childToDelete);
215 | }
216 |
217 | // 跟踪需要移除的子树中的 Fiber 节点
218 | const rootChildrenToDelete: FiberNode[] = [];
219 |
220 | // 递归遍历子树
221 | commitNestedUnmounts(childToDelete, (unmountFiber) => {
222 | switch (unmountFiber.tag) {
223 | case HostComponent:
224 | recordChildrenToDelete(rootChildrenToDelete, unmountFiber);
225 | // TODO 解绑ref
226 | return;
227 | case HostText:
228 | recordChildrenToDelete(rootChildrenToDelete, unmountFiber);
229 | return;
230 | case FunctionComponent:
231 | commitPassiveEffect(unmountFiber, root, 'unmount');
232 | return;
233 | default:
234 | if (__DEV__) {
235 | console.warn('未实现的 delete 类型', unmountFiber);
236 | }
237 | }
238 | });
239 |
240 | // 移除 rootChildrenToDelete 的DOM
241 | if (rootChildrenToDelete.length !== 0) {
242 | // 找到待删除子树的根节点的 parent DOM
243 | const hostParent = getHostParent(childToDelete) as Container;
244 | rootChildrenToDelete.forEach((node) => {
245 | removeChild(node.stateNode, hostParent);
246 | });
247 | }
248 |
249 | childToDelete.return = null;
250 | childToDelete.child = null;
251 | };
252 |
253 | // 深度优先遍历 Fiber 树,执行 onCommitUnmount
254 | const commitNestedUnmounts = (
255 | root: FiberNode,
256 | onCommitUnmount: (unmountFiber: FiberNode) => void
257 | ) => {
258 | let node = root;
259 | while (true) {
260 | onCommitUnmount(node);
261 |
262 | // 向下遍历,递
263 | if (node.child !== null) {
264 | node.child.return = node;
265 | node = node.child;
266 | continue;
267 | }
268 | // 终止条件
269 | if (node === root) return;
270 |
271 | // 向上遍历,归
272 | while (node.sibling === null) {
273 | // 终止条件
274 | if (node.return == null || node.return == root) return;
275 | node = node.return;
276 | }
277 | node.sibling.return = node.return;
278 | node = node.sibling;
279 | }
280 | };
281 |
282 | const commitPassiveEffect = (
283 | fiber: FiberNode,
284 | root: FiberRootNode,
285 | type: keyof PendingPassiveEffects
286 | ) => {
287 | if (
288 | fiber.tag !== FunctionComponent ||
289 | (type == 'update' && (fiber.flags & PassiveEffect) == NoFlags)
290 | ) {
291 | return;
292 | }
293 | const updateQueue = fiber.updateQueue as FCUpdateQueue;
294 | if (updateQueue !== null) {
295 | if (updateQueue.lastEffect == null && __DEV__) {
296 | console.error('commitPassiveEffect: updateQueue.lastEffect is null');
297 | } else {
298 | root.pendingPassiveEffects[type].push(updateQueue.lastEffect as Effect);
299 | }
300 | }
301 | };
302 |
303 | const commitHookEffectList = (
304 | tags: EffectTags,
305 | lastEffect: Effect,
306 | callback: (effect: Effect) => void
307 | ) => {
308 | let effect = lastEffect.next as Effect;
309 |
310 | do {
311 | if ((effect.tag & tags) === tags) {
312 | callback(effect);
313 | }
314 | effect = effect.next as Effect;
315 | } while (effect !== lastEffect.next);
316 | };
317 |
318 | // 组件卸载时,触发所有 unmount destroy
319 | export function commitHookEffectListUnmount(
320 | tags: EffectTags,
321 | lastEffect: Effect
322 | ) {
323 | commitHookEffectList(tags, lastEffect, (effect) => {
324 | const destroy = effect.destroy;
325 | if (typeof destroy === 'function') {
326 | destroy();
327 | }
328 | effect.tag &= ~HookHasEffect;
329 | });
330 | }
331 |
332 | // 组件卸载时,触发所有上次更新的 destroy
333 | export function commitHookEffectListDestory(
334 | tags: EffectTags,
335 | lastEffect: Effect
336 | ) {
337 | commitHookEffectList(tags, lastEffect, (effect) => {
338 | const destroy = effect.destroy;
339 | if (typeof destroy === 'function') {
340 | destroy();
341 | }
342 | });
343 | }
344 |
345 | // 组件卸载时,触发所有这次更新的 create
346 | export function commitHookEffectListCreate(
347 | tags: EffectTags,
348 | lastEffect: Effect
349 | ) {
350 | commitHookEffectList(tags, lastEffect, (effect) => {
351 | const create = effect.create;
352 | if (typeof create === 'function') {
353 | effect.destroy = create();
354 | }
355 | });
356 | }
357 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/completeWork.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Container,
3 | Instance,
4 | appendInitialChild,
5 | createInstance,
6 | createTextInstance
7 | } from 'hostConfig';
8 | import { FiberNode } from './fiber';
9 | import {
10 | Fragment,
11 | FunctionComponent,
12 | HostComponent,
13 | HostRoot,
14 | HostText
15 | } from './workTags';
16 | import { NoFlags, Update } from './fiberFlags';
17 |
18 | // 生成更新计划,计算和收集更新 flags
19 | export const completeWork = (workInProgress: FiberNode) => {
20 | const newProps = workInProgress.pendingProps;
21 | const current = workInProgress.alternate;
22 | switch (workInProgress.tag) {
23 | case HostRoot:
24 | case FunctionComponent:
25 | case Fragment:
26 | bubbleProperties(workInProgress);
27 | return null;
28 |
29 | case HostComponent:
30 | if (current !== null && workInProgress.stateNode != null) {
31 | // 组件的更新阶段
32 | updateHostComponent(current, workInProgress);
33 | } else {
34 | // 首屏渲染阶段
35 | // 构建 DOM
36 | const instance = createInstance(workInProgress.type, newProps);
37 | // 将 DOM 插入到 DOM 树中
38 | appendAllChildren(instance, workInProgress);
39 | workInProgress.stateNode = instance;
40 | }
41 | // 收集更新 flags
42 | bubbleProperties(workInProgress);
43 | return null;
44 |
45 | case HostText:
46 | if (current !== null && workInProgress.stateNode !== null) {
47 | // 组件的更新阶段
48 | updateHostText(current, workInProgress);
49 | } else {
50 | // 首屏渲染阶段
51 | // 构建 DOM
52 | const instance = createTextInstance(newProps.content);
53 | workInProgress.stateNode = instance;
54 | }
55 | // 收集更新 flags
56 | bubbleProperties(workInProgress);
57 | return null;
58 |
59 | default:
60 | if (__DEV__) {
61 | console.warn('completeWork 未实现的类型', workInProgress);
62 | }
63 | break;
64 | }
65 | };
66 |
67 | function updateHostText(current: FiberNode, workInProgress: FiberNode) {
68 | const oldText = current.memoizedProps.content;
69 | const newText = workInProgress.pendingProps.content;
70 | if (oldText !== newText) {
71 | markUpdate(workInProgress);
72 | }
73 | }
74 |
75 | function updateHostComponent(current: FiberNode, workInProgress: FiberNode) {
76 | markUpdate(workInProgress);
77 | }
78 |
79 | // 为 Fiber 节点增加 Update flags
80 | function markUpdate(workInProgress: FiberNode) {
81 | workInProgress.flags |= Update;
82 | }
83 |
84 | function appendAllChildren(
85 | parent: Container | Instance,
86 | workInProgress: FiberNode
87 | ) {
88 | let node = workInProgress.child;
89 | while (node !== null) {
90 | if (node.tag == HostComponent || node.tag == HostText) {
91 | // 处理原生 DOM 元素节点或文本节点
92 | appendInitialChild(parent, node.stateNode);
93 | } else if (node.child !== null) {
94 | // 递归处理其他类型的组件节点的子节点
95 | node.child.return = node;
96 | node = node.child;
97 | continue;
98 | }
99 | if (node == workInProgress) {
100 | return;
101 | }
102 |
103 | while (node.sibling === null) {
104 | if (node.return === null || node.return === workInProgress) {
105 | return;
106 | }
107 | node = node.return;
108 | }
109 | // 处理下一个兄弟节点
110 | node.sibling.return = node.return;
111 | node = node.sibling;
112 | }
113 | }
114 |
115 | // 收集更新 flags,将子 FiberNode 的 flags 冒泡到父 FiberNode 上
116 | function bubbleProperties(workInProgress: FiberNode) {
117 | let subtreeFlags = NoFlags;
118 | let child = workInProgress.child;
119 | while (child !== null) {
120 | subtreeFlags |= child.subtreeFlags;
121 | subtreeFlags |= child.flags;
122 |
123 | child.return = workInProgress;
124 | child = child.sibling;
125 | }
126 |
127 | workInProgress.subtreeFlags |= subtreeFlags;
128 | }
129 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiber.ts:
--------------------------------------------------------------------------------
1 | import { Props, Key, Ref, ReactElementType } from 'shared/ReactTypes';
2 | import {
3 | FunctionComponent,
4 | HostComponent,
5 | Fragment,
6 | WorkTag
7 | } from './workTags';
8 | import { NoFlags, Flags } from './fiberFlags';
9 | import { Container } from 'hostConfig';
10 | import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes';
11 | import { Effect } from './fiberHooks';
12 | import { CallbackNode } from 'scheduler';
13 |
14 | export class FiberNode {
15 | tag: WorkTag;
16 | key: Key | null;
17 | stateNode: any;
18 | type: any;
19 | return: FiberNode | null;
20 | sibling: FiberNode | null;
21 | child: FiberNode | null;
22 | index: number;
23 | ref: Ref;
24 | pendingProps: Props;
25 | memoizedProps: Props | null;
26 | memoizedState: any;
27 | alternate: FiberNode | null;
28 | flags: Flags;
29 | deletions: Array | null;
30 | subtreeFlags: Flags;
31 | updateQueue: unknown;
32 |
33 | constructor(tag: WorkTag, pendingProps: Props, key: Key) {
34 | // 类型
35 | this.tag = tag;
36 | this.key = key || null;
37 | this.ref = null;
38 | this.stateNode = null; // 节点对应的实际 DOM 节点或组件实例
39 | this.type = null; // 节点的类型,可以是原生 DOM 元素、函数组件或类组件等
40 |
41 | // 构成树状结构
42 | this.return = null; // 指向节点的父节点
43 | this.sibling = null; // 指向节点的下一个兄弟节点
44 | this.child = null; // 指向节点的第一个子节点
45 | this.index = 0; // 索引
46 |
47 | // 作为工作单元
48 | this.pendingProps = pendingProps; // 表示节点的新属性,用于在协调过程中进行更新
49 | this.memoizedProps = null; // 已经更新完的属性
50 | this.memoizedState = null; // 更新完成后新的 State
51 | this.updateQueue = null; // 更新计划队列
52 | this.alternate = null; // 指向节点的备份节点,用于在协调过程中进行比较
53 | this.flags = NoFlags; // 表示节点的副作用类型,如更新、插入、删除等
54 | this.subtreeFlags = NoFlags; // 表示子节点的副作用类型,如更新、插入、删除等
55 | this.deletions = null; // 指向待删除的子节点,用于在协调过程中进行删除
56 | }
57 | }
58 |
59 | export class FiberRootNode {
60 | container: Container;
61 | current: FiberNode;
62 | finishedWork: FiberNode | null;
63 | pendingLanes: Lanes;
64 | finishedLane: Lane;
65 | pendingPassiveEffects: PendingPassiveEffects;
66 | callbackNode: CallbackNode | null;
67 | callbackPriority: Lane;
68 | constructor(container: Container, hostRootFiber: FiberNode) {
69 | this.container = container;
70 | this.current = hostRootFiber;
71 | // 将根节点的 stateNode 属性指向 FiberRootNode,用于表示整个 React 应用的根节点
72 | hostRootFiber.stateNode = this;
73 | // 指向更新完成之后的 hostRootFiber
74 | this.finishedWork = null;
75 | this.pendingLanes = NoLanes;
76 | this.finishedLane = NoLane;
77 | this.pendingPassiveEffects = {
78 | unmount: [],
79 | update: []
80 | };
81 | this.callbackNode = null;
82 | this.callbackPriority = NoLane;
83 | }
84 | }
85 |
86 | export interface PendingPassiveEffects {
87 | unmount: Effect[];
88 | update: Effect[];
89 | }
90 |
91 | // 根据 FiberRootNode.current 创建 workInProgress
92 | export const createWorkInProgress = (
93 | current: FiberNode,
94 | pendingProps: Props
95 | ): FiberNode => {
96 | let workInProgress = current.alternate;
97 | if (workInProgress == null) {
98 | // 首屏渲染时(mount)
99 | workInProgress = new FiberNode(current.tag, pendingProps, current.key);
100 | workInProgress.stateNode = current.stateNode;
101 |
102 | // 双缓冲机制
103 | workInProgress.alternate = current;
104 | current.alternate = workInProgress;
105 | } else {
106 | // 非首屏渲染时(update)
107 | workInProgress.pendingProps = pendingProps;
108 | // 将 effect 链表重置为空,以便在更新过程中记录新的副作用
109 | workInProgress.flags = NoFlags;
110 | workInProgress.subtreeFlags = NoFlags;
111 | }
112 | // 复制当前节点的大部分属性
113 | workInProgress.type = current.type;
114 | workInProgress.updateQueue = current.updateQueue;
115 | workInProgress.child = current.child;
116 | workInProgress.memoizedProps = current.memoizedProps;
117 | workInProgress.memoizedState = current.memoizedState;
118 |
119 | return workInProgress;
120 | };
121 |
122 | // 根据 DOM 节点创建新的 Fiber 节点
123 | export function createFiberFromElement(element: ReactElementType): FiberNode {
124 | const { type, key, props } = element;
125 | let fiberTag: WorkTag = FunctionComponent;
126 | if (typeof type == 'string') {
127 | // 如: 的 type: 'div'
128 | fiberTag = HostComponent;
129 | } else if (typeof type !== 'function' && __DEV__) {
130 | console.warn('未定义的 type 类型', element);
131 | }
132 |
133 | const fiber = new FiberNode(fiberTag, props, key);
134 | fiber.type = type;
135 | return fiber;
136 | }
137 |
138 | export function createFiberFromFragment(elements: any[], key: Key): FiberNode {
139 | const fiber = new FiberNode(Fragment, elements, key);
140 | return fiber;
141 | }
142 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberFlags.ts:
--------------------------------------------------------------------------------
1 | // 保存在 Fiber.flags 中的 flags
2 | export type Flags = number;
3 |
4 | export const NoFlags = 0b0000000;
5 | export const PerformedWork = 0b0000001;
6 | export const Placement = 0b0000010;
7 | export const Update = 0b0000100;
8 | export const ChildDeletion = 0b0001000;
9 | export const PassiveEffect = 0b0010000; // Fiber 节点本次更新存在副作用
10 |
11 | export const MutationMask = Placement | Update | ChildDeletion;
12 |
13 | // useEffect 的依赖变化时,或函数组件卸载时,执行回调
14 | export const PassiveMask = PassiveEffect | ChildDeletion;
15 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberHooks.ts:
--------------------------------------------------------------------------------
1 | import internals from 'shared/internals';
2 | import { FiberNode } from './fiber';
3 | import {
4 | Update,
5 | UpdateQueue,
6 | createUpdate,
7 | enqueueUpdate,
8 | processUpdateQueue
9 | } from './updateQueue';
10 | import { Dispatch, Dispatcher } from 'react/src/currentDispatcher';
11 | import { createUpdateQueue } from './updateQueue';
12 | import { Action } from 'shared/ReactTypes';
13 | import { scheduleUpdateOnFiber } from './workLoop';
14 | import { Lane, NoLane, requestUpdateLanes } from './fiberLanes';
15 | import { EffectTags, HookHasEffect, Passive } from './hookEffectTags';
16 | import { PassiveEffect } from './fiberFlags';
17 |
18 | // 当前正在被处理的 FiberNode
19 | let currentlyRenderingFiber: FiberNode | null = null;
20 | // Hooks 链表中当前正在工作的 Hook
21 | let workInProgressHook: Hook | null = null;
22 | let currentHook: Hook | null = null;
23 | let renderLane: Lane = NoLane;
24 |
25 | const { currentDispatcher } = internals;
26 |
27 | // 定义 Hook 数据结构
28 | export interface Hook {
29 | memoizedState: any; // 保存 Hook 的数据
30 | queue: any;
31 | next: Hook | null;
32 | baseState: any;
33 | baseQueue: Update | null;
34 | }
35 |
36 | export interface Effect {
37 | tag: EffectTags;
38 | create: EffectCallback | void;
39 | destroy: EffectCallback | void;
40 | deps: EffectDeps;
41 | next: Effect | null;
42 | }
43 |
44 | type EffectCallback = () => void;
45 | type EffectDeps = any[] | null;
46 |
47 | // 定义函数组件的 FCUpdateQueue 数据结构
48 | export interface FCUpdateQueue extends UpdateQueue {
49 | lastEffect: Effect | null;
50 | }
51 |
52 | // 执行函数组件中的函数
53 | export function renderWithHooks(workInProgress: FiberNode, lane: Lane) {
54 | // 赋值
55 | currentlyRenderingFiber = workInProgress;
56 | renderLane = lane;
57 |
58 | // 重置 Hooks 链表
59 | workInProgress.memoizedState = null;
60 | // 重置 Effect 链表
61 | workInProgress.updateQueue = null;
62 |
63 | // 判断 Hooks 被调用的时机
64 | const current = workInProgress.alternate;
65 | if (__DEV__) {
66 | console.warn(current !== null ? '组件的更新阶段' : '首屏渲染阶段');
67 | }
68 | if (current !== null) {
69 | // 组件的更新阶段(update)
70 | currentDispatcher.current = HooksDispatcherOnUpdate;
71 | } else {
72 | // 首屏渲染阶段(mount)
73 | currentDispatcher.current = HooksDispatcherOnMount;
74 | }
75 |
76 | // 函数保存在 type 字段中
77 | const Component = workInProgress.type;
78 | const props = workInProgress.pendingProps;
79 | // 执行函数
80 | const children = Component(props);
81 |
82 | // 重置
83 | currentlyRenderingFiber = null;
84 | workInProgressHook = null;
85 | currentHook = null;
86 | renderLane = NoLane;
87 |
88 | return children;
89 | }
90 |
91 | const HooksDispatcherOnMount: Dispatcher = {
92 | useState: mountState,
93 | useEffect: mountEffect
94 | };
95 |
96 | const HooksDispatcherOnUpdate: Dispatcher = {
97 | useState: updateState,
98 | useEffect: updateEffect
99 | };
100 |
101 | function mountState(
102 | initialState: (() => State) | State
103 | ): [State, Dispatch] {
104 | if (__DEV__) {
105 | console.warn('调用 mountState 方法');
106 | }
107 |
108 | // 当前正在工作的 useState
109 | const hook = mountWorkInProgressHook();
110 | // 获取当前 useState 对应的 Hook 数据
111 | let memoizedState;
112 | if (initialState instanceof Function) {
113 | memoizedState = initialState();
114 | } else {
115 | memoizedState = initialState;
116 | }
117 | hook.memoizedState = memoizedState;
118 |
119 | const queue = createUpdateQueue();
120 | hook.queue = queue;
121 |
122 | // @ts-ignore
123 | // 实现 dispatch
124 | const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
125 | queue.dispatch = dispatch;
126 |
127 | return [memoizedState, dispatch];
128 | }
129 |
130 | function updateState(): [State, Dispatch] {
131 | if (__DEV__) {
132 | console.warn('调用 updateState 方法');
133 | }
134 | // 当前正在工作的 useState
135 | const hook = updateWorkInProgressHook();
136 |
137 | // 计算新 state 的逻辑
138 | const queue = hook.queue as UpdateQueue;
139 | const baseState = hook.baseState;
140 | const pending = queue.shared.pending;
141 | const current = currentHook as Hook;
142 | let baseQueue = current.baseQueue;
143 |
144 | if (pending !== null) {
145 | if (baseQueue !== null) {
146 | // 合并 baseQueue 和 queue 链表
147 | const baseQueueFirst = baseQueue.next;
148 | const pendingFirst = pending.next;
149 | baseQueue.next = pendingFirst;
150 | pending.next = baseQueueFirst;
151 | }
152 | baseQueue = pending;
153 | // 将 baseQueue 保存在 current
154 | current.baseQueue = pending;
155 | queue.shared.pending = null;
156 |
157 | if (baseQueue !== null) {
158 | const {
159 | memoizedState,
160 | baseQueue: newBaseQueue,
161 | baseState: newBaseState
162 | } = processUpdateQueue(baseState, baseQueue, renderLane);
163 | hook.memoizedState = memoizedState;
164 | hook.baseQueue = newBaseQueue;
165 | hook.baseState = newBaseState;
166 | }
167 | }
168 | return [hook.memoizedState, queue.dispatch as Dispatch];
169 | }
170 |
171 | function mountEffect(create: EffectCallback | void, deps: EffectDeps | void) {
172 | // 当前正在工作的 useEffect
173 | const hook = mountWorkInProgressHook();
174 | const nextDeps = deps == undefined ? null : deps;
175 |
176 | (currentlyRenderingFiber as FiberNode).flags |= PassiveEffect;
177 | hook.memoizedState = pushEffect(
178 | Passive | HookHasEffect,
179 | create,
180 | undefined,
181 | nextDeps
182 | );
183 | }
184 |
185 | function updateEffect(create: EffectCallback | void, deps: EffectDeps | void) {
186 | // 当前正在工作的 useEffect
187 | const hook = updateWorkInProgressHook();
188 | const nextDeps = deps == undefined ? null : (deps as EffectDeps);
189 | let destroy: EffectCallback | void;
190 |
191 | if (currentHook !== null) {
192 | const prevEffect = currentHook.memoizedState as Effect;
193 | destroy = prevEffect.destroy;
194 | if (nextDeps !== null) {
195 | // 浅比较依赖
196 | const prevDeps = prevEffect.deps;
197 | // 浅比较,相等
198 | if (areHookInputsEqual(nextDeps, prevDeps)) {
199 | hook.memoizedState = pushEffect(Passive, create, destroy, nextDeps);
200 | return;
201 | }
202 | // 浅比较,不相等
203 | (currentlyRenderingFiber as FiberNode).flags |= PassiveEffect;
204 | hook.memoizedState = pushEffect(
205 | Passive | HookHasEffect,
206 | create,
207 | destroy,
208 | nextDeps
209 | );
210 | }
211 | }
212 | }
213 |
214 | function areHookInputsEqual(
215 | nextDeps: EffectDeps,
216 | prevDeps: EffectDeps
217 | ): boolean {
218 | if (nextDeps === null || prevDeps === null) return false;
219 | for (let i = 0; i < nextDeps.length && i < prevDeps.length; i++) {
220 | if (Object.is(nextDeps[i], prevDeps[i])) {
221 | continue;
222 | }
223 | return false;
224 | }
225 | return true;
226 | }
227 |
228 | function pushEffect(
229 | tag: EffectTags,
230 | create: EffectCallback | void,
231 | destroy: EffectCallback | void,
232 | deps: EffectDeps
233 | ): Effect {
234 | const effect: Effect = {
235 | tag,
236 | create,
237 | destroy,
238 | deps,
239 | next: null
240 | };
241 | const fiber = currentlyRenderingFiber as FiberNode;
242 | const updateQueue = fiber.updateQueue as FCUpdateQueue;
243 | if (updateQueue === null) {
244 | const newUpdateQueue = creactFCUpdateQueue();
245 | effect.next = effect;
246 | newUpdateQueue.lastEffect = effect;
247 | fiber.updateQueue = newUpdateQueue;
248 | } else {
249 | const lastEffect = updateQueue.lastEffect;
250 | if (lastEffect == null) {
251 | effect.next = effect;
252 | updateQueue.lastEffect = effect;
253 | } else {
254 | const firstEffect = lastEffect.next;
255 | lastEffect.next = effect;
256 | effect.next = firstEffect;
257 | updateQueue.lastEffect = effect;
258 | }
259 | }
260 | return effect;
261 | }
262 |
263 | function creactFCUpdateQueue() {
264 | const updateQueue = createUpdateQueue() as FCUpdateQueue;
265 | updateQueue.lastEffect = null;
266 | return updateQueue;
267 | }
268 |
269 | function updateWorkInProgressHook(): Hook {
270 | // TODO render 阶段触发的更新
271 | // 保存链表中的下一个 Hook
272 | let nextCurrentHook: Hook | null;
273 | if (currentHook == null) {
274 | // 这是函数组件 update 时的第一个 hook
275 | const current = (currentlyRenderingFiber as FiberNode).alternate;
276 | if (current === null) {
277 | nextCurrentHook = null;
278 | } else {
279 | nextCurrentHook = current.memoizedState;
280 | }
281 | } else {
282 | // 这是函数组件 update 时后续的 hook
283 | nextCurrentHook = currentHook.next;
284 | }
285 |
286 | if (nextCurrentHook == null) {
287 | throw new Error(
288 | `组件 ${currentlyRenderingFiber?.type} 本次执行时的 Hooks 比上次执行多`
289 | );
290 | }
291 |
292 | currentHook = nextCurrentHook as Hook;
293 | const newHook: Hook = {
294 | memoizedState: currentHook.memoizedState,
295 | queue: currentHook.queue,
296 | next: null,
297 | baseQueue: currentHook.baseQueue,
298 | baseState: currentHook.baseState
299 | };
300 | if (workInProgressHook == null) {
301 | // update 时的第一个hook
302 | if (currentlyRenderingFiber !== null) {
303 | workInProgressHook = newHook;
304 | currentlyRenderingFiber.memoizedState = workInProgressHook;
305 | } else {
306 | // currentlyRenderingFiber == null 代表 Hook 执行的上下文不是一个函数组件
307 | throw new Error('Hooks 只能在函数组件中执行');
308 | }
309 | } else {
310 | // update 时的其他 hook
311 | // 将当前处理的 Hook.next 指向新建的 hook,形成 Hooks 链表
312 | workInProgressHook.next = newHook;
313 | // 更新当前处理的 Hook
314 | workInProgressHook = newHook;
315 | }
316 | return workInProgressHook;
317 | }
318 |
319 | function mountWorkInProgressHook(): Hook {
320 | const hook: Hook = {
321 | memoizedState: null,
322 | queue: null,
323 | next: null,
324 | baseQueue: null,
325 | baseState: null
326 | };
327 | if (workInProgressHook == null) {
328 | // mount 时的第一个hook
329 | if (currentlyRenderingFiber !== null) {
330 | workInProgressHook = hook;
331 | currentlyRenderingFiber.memoizedState = workInProgressHook;
332 | } else {
333 | // currentlyRenderingFiber == null 代表 Hook 执行的上下文不是一个函数组件
334 | throw new Error('Hooks 只能在函数组件中执行');
335 | }
336 | } else {
337 | // mount 时的其他 hook
338 | // 将当前处理的 Hook.next 指向新建的 hook,形成 Hooks 链表
339 | workInProgressHook.next = hook;
340 | // 更新当前处理的 Hook
341 | workInProgressHook = hook;
342 | }
343 | return workInProgressHook;
344 | }
345 |
346 | // 用于触发状态更新的逻辑
347 | function dispatchSetState(
348 | fiber: FiberNode,
349 | updateQueue: UpdateQueue,
350 | action: Action
351 | ) {
352 | const lane = requestUpdateLanes();
353 | const update = createUpdate(action, lane);
354 | enqueueUpdate(updateQueue, update);
355 | // 调度更新
356 | scheduleUpdateOnFiber(fiber, lane);
357 | }
358 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberLanes.ts:
--------------------------------------------------------------------------------
1 | import {
2 | unstable_IdlePriority,
3 | unstable_ImmediatePriority,
4 | unstable_NormalPriority,
5 | unstable_UserBlockingPriority,
6 | unstable_getCurrentPriorityLevel
7 | } from 'scheduler';
8 | import { FiberRootNode } from './fiber';
9 |
10 | // 代表 update 的优先级
11 | export type Lane = number;
12 | // 代表 lane 的集合
13 | export type Lanes = number;
14 |
15 | export const NoLane = 0b0000;
16 | export const NoLanes = 0b0000;
17 |
18 | export const SyncLane = 0b0001;
19 | export const InputContinuousLane = 0b0010;
20 | export const DefaultLane = 0b0100;
21 | export const IdleLane = 0b1000;
22 |
23 | export function mergeLanes(laneA: Lane, laneB: Lane): Lanes {
24 | return laneA | laneB;
25 | }
26 |
27 | // 获取更新的优先级
28 | export function requestUpdateLanes() {
29 | // 从上下文环境中获取 Scheduler 优先级
30 | const currentSchedulerPriority = unstable_getCurrentPriorityLevel();
31 | const lane = schedulerPriorityToLane(currentSchedulerPriority);
32 | return lane;
33 | }
34 |
35 | // 获取 lanes 中优先级最高的 lane
36 | export function getHighestPriorityLane(lanes: Lanes): Lane {
37 | // 默认规则:数值越小,优先级越高
38 | return lanes & -lanes;
39 | }
40 |
41 | // 从根节点的 pendingLanes 中移除某个 lane
42 | export function markRootFinished(root: FiberRootNode, lane: Lane): void {
43 | root.pendingLanes &= ~lane;
44 | }
45 |
46 | // 判断优先级是否足够高
47 | export function isSubsetOfLanes(set: Lanes, subset: Lane): boolean {
48 | return (set & subset) === subset;
49 | }
50 |
51 | export function laneToSchedulerPriority(lanes: number): number {
52 | const lane = getHighestPriorityLane(lanes);
53 | if (lane == SyncLane) {
54 | return unstable_ImmediatePriority;
55 | }
56 | if (lane == InputContinuousLane) {
57 | return unstable_UserBlockingPriority;
58 | }
59 | if (lane == DefaultLane) {
60 | return unstable_NormalPriority;
61 | }
62 | return unstable_IdlePriority;
63 | }
64 |
65 | export function schedulerPriorityToLane(schedulerPriority: number): number {
66 | if (schedulerPriority == unstable_ImmediatePriority) {
67 | return SyncLane;
68 | }
69 | if (schedulerPriority == unstable_UserBlockingPriority) {
70 | return InputContinuousLane;
71 | }
72 | if (schedulerPriority == unstable_NormalPriority) {
73 | return DefaultLane;
74 | }
75 | return NoLane;
76 | }
77 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberReconciler.ts:
--------------------------------------------------------------------------------
1 | import { Container } from 'hostConfig';
2 | import { FiberNode, FiberRootNode } from './fiber';
3 | import { HostRoot } from './workTags';
4 | import {
5 | UpdateQueue,
6 | createUpdate,
7 | createUpdateQueue,
8 | enqueueUpdate
9 | } from './updateQueue';
10 | import { ReactElementType } from 'shared/ReactTypes';
11 | import { scheduleUpdateOnFiber } from './workLoop';
12 | import { requestUpdateLanes } from './fiberLanes';
13 |
14 | export function createContainer(container: Container) {
15 | const hostRootFiber = new FiberNode(HostRoot, {}, null);
16 | const root = new FiberRootNode(container, hostRootFiber);
17 | hostRootFiber.updateQueue = createUpdateQueue();
18 | return root;
19 | }
20 |
21 | export function updateContainer(
22 | element: ReactElementType | null,
23 | root: FiberRootNode
24 | ) {
25 | const hostRootFiber = root.current;
26 | const lane = requestUpdateLanes();
27 | const update = createUpdate(element, lane);
28 | enqueueUpdate(
29 | hostRootFiber.updateQueue as UpdateQueue,
30 | update
31 | );
32 | scheduleUpdateOnFiber(hostRootFiber, lane);
33 | return element;
34 | }
35 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/hookEffectTags.ts:
--------------------------------------------------------------------------------
1 | // 保存在 Effect.tag 中的 tags
2 | export type EffectTags = number;
3 |
4 | // Fiber 节点本次更新存在副作用
5 | export const HookHasEffect = 0b0001;
6 |
7 | export const Passive = 0b0010; // useEffect
8 | export const Layout = 0b0100; // useLayoutEffect
9 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/reconciler.d.ts:
--------------------------------------------------------------------------------
1 | declare let __DEV__: boolean;
2 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/syncTaskQueue.ts:
--------------------------------------------------------------------------------
1 | // 同步的任务队列
2 | let syncQueue: ((...args: any) => void)[] | null = null;
3 | let isFlushingSyncQueue: boolean = false;
4 |
5 | // 调度同步的回调函数
6 | export function scheduleSyncCallback(callback: (...args: any) => void) {
7 | if (syncQueue === null) {
8 | syncQueue = [callback];
9 | }
10 | syncQueue.push(callback);
11 | }
12 |
13 | // 遍历执行同步的回调函数
14 | export function flushSyncCallback() {
15 | if (!isFlushingSyncQueue && syncQueue) {
16 | isFlushingSyncQueue = true;
17 | try {
18 | syncQueue.forEach((callback) => callback());
19 | } catch (e) {
20 | if (__DEV__) {
21 | console.error('flushSyncCallback 报错', e);
22 | }
23 | } finally {
24 | isFlushingSyncQueue = false;
25 | syncQueue = null;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/updateQueue.ts:
--------------------------------------------------------------------------------
1 | import { Action } from 'shared/ReactTypes';
2 | import { Update } from './fiberFlags';
3 | import { Dispatch } from 'react/src/currentDispatcher';
4 | import { Lane, NoLane, isSubsetOfLanes } from './fiberLanes';
5 |
6 | // 定义 Update 数据结构
7 | export interface Update {
8 | action: Action;
9 | next: Update | null;
10 | lane: Lane;
11 | }
12 |
13 | // 定义 UpdateQueue 数据结构
14 | export interface UpdateQueue {
15 | shared: {
16 | pending: Update | null;
17 | };
18 | dispatch: Dispatch | null;
19 | }
20 |
21 | // 创建 Update 实例的方法
22 | export const createUpdate = (
23 | action: Action,
24 | lane: Lane
25 | ): Update => {
26 | return {
27 | action,
28 | next: null,
29 | lane
30 | };
31 | };
32 |
33 | // 创建 UpdateQueue 实例的方法
34 | export const createUpdateQueue = (): UpdateQueue => {
35 | return {
36 | shared: {
37 | pending: null
38 | },
39 | dispatch: null
40 | };
41 | };
42 |
43 | // 将 Update 添加到 UpdateQueue 中的方法
44 | export const enqueueUpdate = (
45 | updateQueue: UpdateQueue,
46 | update: Update
47 | ) => {
48 | const pending = updateQueue.shared.pending;
49 | if (pending === null) {
50 | update.next = update;
51 | } else {
52 | update.next = pending.next;
53 | pending.next = update;
54 | }
55 | // pending 指向 update 环状链表的最后一个节点
56 | updateQueue.shared.pending = update;
57 | };
58 |
59 | // 从 UpdateQueue 中消费 Update 的方法
60 | export const processUpdateQueue = (
61 | baseState: State,
62 | pendingUpdate: Update | null,
63 | renderLane: Lane
64 | ): {
65 | memoizedState: State;
66 | baseState: State;
67 | baseQueue: Update | null;
68 | } => {
69 | const result: ReturnType> = {
70 | memoizedState: baseState,
71 | baseState,
72 | baseQueue: null
73 | };
74 | if (pendingUpdate !== null) {
75 | // 第一个 update
76 | const first = pendingUpdate.next;
77 | let pending = first as Update;
78 |
79 | let newBaseState = baseState; // 消费本次 Update 后的 baseState
80 | let newState = baseState; // 消费本次 Update 后计算后的结果
81 | let newBaseQueueFirst: Update | null = null; // 消费本次 Update 后的 baseQueue 链表头
82 | let newBaseQueueLast: Update | null = null; // 消费本次 Update 后的 baseQueue 链表尾
83 |
84 | do {
85 | const updateLane = pending.lane;
86 | if (!isSubsetOfLanes(renderLane, updateLane)) {
87 | // 优先级不够,跳过本次 Update
88 | const clone = createUpdate(pending.action, pending.lane);
89 | // 判断之前是否存在被跳过的 Update
90 | if (newBaseQueueLast === null) {
91 | newBaseQueueFirst = clone;
92 | // 若有更新被跳过,baseState 为最后一个没有被跳过的 Update 计算后的结果
93 | newBaseState = newState;
94 | } else {
95 | // 本次更新第一个被跳过的 Update 及其后面的所有 Update 都会被保存在 baseQueue 中参与下次 State 计算
96 | newBaseQueueLast.next = clone;
97 | }
98 | newBaseQueueLast = clone;
99 | } else {
100 | // 优先级足够
101 | // 判断之前是否存在被跳过的 Update
102 | if (newBaseQueueLast !== null) {
103 | // 本次更新参与计算但保存在 baseQueue 中的 Update,优先级会降低到 NoLane
104 | const clone = createUpdate(pending.action, NoLane);
105 | newBaseQueueLast.next = clone;
106 | newBaseQueueLast = clone;
107 | }
108 |
109 | const action = pending.action;
110 | if (action instanceof Function) {
111 | // 若 action 是回调函数:(baseState = 1, update = (i) => 5i)) => memoizedState = 5
112 | newState = action(baseState);
113 | } else {
114 | // 若 action 是状态值:(baseState = 1, update = 2) => memoizedState = 2
115 | newState = action;
116 | }
117 | }
118 | pending = pending.next as Update;
119 | } while (pending !== first);
120 |
121 | if (newBaseQueueLast === null) {
122 | // 本次更新没有 Update 被跳过
123 | newBaseState = newState;
124 | } else {
125 | // 本次更新有 Update 被跳过
126 | // 将 baseQueue 变成环状链表
127 | newBaseQueueLast.next = newBaseQueueFirst;
128 | }
129 | result.memoizedState = newState;
130 | result.baseState = newBaseState;
131 | result.baseQueue = newBaseQueueFirst;
132 | }
133 |
134 | return result;
135 | };
136 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/workLoop.ts:
--------------------------------------------------------------------------------
1 | import {
2 | FiberNode,
3 | FiberRootNode,
4 | PendingPassiveEffects,
5 | createWorkInProgress
6 | } from './fiber';
7 | import { beginWork } from './beginWork';
8 | import { completeWork } from './completeWork';
9 | import { HostRoot } from './workTags';
10 | import { MutationMask, NoFlags, PassiveMask } from './fiberFlags';
11 | import {
12 | commitHookEffectListCreate,
13 | commitHookEffectListDestory,
14 | commitHookEffectListUnmount,
15 | commitMutationEffects
16 | } from './commitWork';
17 | import {
18 | Lane,
19 | NoLane,
20 | SyncLane,
21 | getHighestPriorityLane,
22 | laneToSchedulerPriority,
23 | markRootFinished,
24 | mergeLanes
25 | } from './fiberLanes';
26 | import { flushSyncCallback, scheduleSyncCallback } from './syncTaskQueue';
27 | import { scheduleMicroTask } from 'hostConfig';
28 | import {
29 | unstable_scheduleCallback as scheduleCallback,
30 | unstable_NormalPriority as NormalPriority,
31 | unstable_shouldYield,
32 | unstable_cancelCallback
33 | } from 'scheduler';
34 | import { HookHasEffect, Passive } from './hookEffectTags';
35 |
36 | let workInProgress: FiberNode | null = null;
37 | let workInProgressRenderLane: Lane = NoLane;
38 | let rootDoesHasPassiveEffects: boolean = false;
39 |
40 | type RootExitStatus = number;
41 | const RootIncomplete = 1; // 中断
42 | const RootComplete = 2; // 执行完了
43 |
44 | // 调度功能
45 | export function scheduleUpdateOnFiber(fiber: FiberNode, lane: Lane) {
46 | const root = markUpdateFromFiberToRoot(fiber);
47 | markRootUpdated(root, lane);
48 | ensureRootIsScheduled(root);
49 | }
50 |
51 | // 从触发更新的节点向上遍历到 FiberRootNode
52 | function markUpdateFromFiberToRoot(fiber: FiberNode) {
53 | let node = fiber;
54 | let parent = node.return;
55 | while (parent !== null) {
56 | node = parent;
57 | parent = node.return;
58 | }
59 | if (node.tag == HostRoot) {
60 | return node.stateNode;
61 | }
62 | return null;
63 | }
64 |
65 | // 将更新的优先级(lane)记录到根节点上
66 | function markRootUpdated(root: FiberRootNode, lane: Lane) {
67 | root.pendingLanes = mergeLanes(root.pendingLanes, lane);
68 | }
69 |
70 | // Schedule 阶段入口
71 | function ensureRootIsScheduled(root: FiberRootNode) {
72 | const updateLane = getHighestPriorityLane(root.pendingLanes);
73 | const existingCallback = root.callbackNode;
74 |
75 | // 没有更新了,重置并 return
76 | if (updateLane == NoLane) {
77 | if (existingCallback !== null) {
78 | unstable_cancelCallback(existingCallback);
79 | }
80 | root.callbackNode = null;
81 | root.callbackPriority = NoLane;
82 | return;
83 | }
84 |
85 | const curPriority = updateLane;
86 | const prevPriority = root.callbackPriority;
87 |
88 | // 同优先级的更新,不需要重新调度
89 | if (curPriority === prevPriority) return;
90 |
91 | // 否则,代表有更高优先级的更新插入,如果之前的调度存在,则取消之前的调度
92 | if (existingCallback !== null) {
93 | unstable_cancelCallback(existingCallback);
94 | }
95 | let newCallbackNode = null;
96 |
97 | if (updateLane === SyncLane) {
98 | // 同步优先级,用微任务调度
99 | if (__DEV__) {
100 | console.log('在微任务中调度,优先级:', updateLane);
101 | }
102 | scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
103 | scheduleMicroTask(flushSyncCallback);
104 | } else {
105 | // 其他优先级,用宏任务调度
106 | const schedulerPriority = laneToSchedulerPriority(updateLane);
107 | newCallbackNode = scheduleCallback(
108 | schedulerPriority,
109 | performConcurrentWorkOnRoot.bind(null, root)
110 | );
111 | }
112 | root.callbackNode = newCallbackNode;
113 | root.callbackPriority = curPriority;
114 | }
115 |
116 | // Render 阶段入口
117 | function renderRoot(root: FiberRootNode, lane: Lane, shouldTimeSlice: boolean) {
118 | if (__DEV__) {
119 | console.warn(`render 阶段开始${shouldTimeSlice ? '并发' : '同步'}更新`);
120 | }
121 |
122 | // 中断再继续时,不用初始化
123 | if (workInProgressRenderLane !== lane) {
124 | // 初始化 workInProgress 变量
125 | prepareFreshStack(root, lane);
126 | }
127 |
128 | do {
129 | try {
130 | // 深度优先遍历
131 | shouldTimeSlice ? workLoopConcurrent() : workLoopSync();
132 | break;
133 | } catch (e) {
134 | console.warn('workLoop发生错误:', e);
135 | workInProgress = null;
136 | }
137 | } while (true);
138 |
139 | // 中断执行
140 | if (shouldTimeSlice && workInProgress !== null) {
141 | return RootIncomplete;
142 | }
143 | // 执行完了
144 | if (!shouldTimeSlice && workInProgress !== null && __DEV__) {
145 | console.error('render 阶段结束时, workInProgress 不应该为 null');
146 | }
147 | return RootComplete;
148 | }
149 |
150 | function performConcurrentWorkOnRoot(
151 | root: FiberRootNode,
152 | didTimeout?: boolean
153 | ): any {
154 | // 由于 useEffect 的回调函数可能触发更高优先级的更新
155 | // 在调度之前需要保证 useEffect 回调已全部执行完
156 | const curCallback = root.callbackNode;
157 | const didFlushPassiveEffect = flushPassiveEffects(root.pendingPassiveEffects);
158 | if (didFlushPassiveEffect) {
159 | // 若 useEffect 回调执行完之后,有更高优先级的更新插入了
160 | if (root.callbackNode !== curCallback) {
161 | return null;
162 | }
163 | }
164 |
165 | const updateLane = getHighestPriorityLane(root.pendingLanes);
166 | const curCallbackNode = root.callbackNode;
167 | if (updateLane == NoLane) return null;
168 |
169 | const needSync = updateLane == SyncLane || didTimeout;
170 | // render 阶段
171 | const exitStatus = renderRoot(root, updateLane, !needSync);
172 |
173 | // render 阶段结束后,进入 commit 阶段
174 | ensureRootIsScheduled(root);
175 |
176 | if (exitStatus == RootIncomplete) {
177 | // 执行中断
178 | if (root.callbackNode !== curCallbackNode) {
179 | // 代表有更高优先级的任务插进来
180 | return null;
181 | }
182 | return performConcurrentWorkOnRoot.bind(null, root);
183 | }
184 | if (exitStatus == RootComplete) {
185 | // 执行完了
186 | // 创建根 Fiber 树的 Root Fiber
187 | const finishedWork = root.current.alternate;
188 | root.finishedWork = finishedWork;
189 | root.finishedLane = updateLane;
190 | workInProgressRenderLane = NoLane;
191 |
192 | // 提交阶段的入口函数
193 | commitRoot(root);
194 | } else if (__DEV__) {
195 | console.error('还未实现的并发更新结束状态');
196 | }
197 | }
198 |
199 | function performSyncWorkOnRoot(root: FiberRootNode) {
200 | const updateLane = getHighestPriorityLane(root.pendingLanes);
201 | if (updateLane !== SyncLane) {
202 | // 其他比 SyncLane 低的优先级或 NoLane,重新调度
203 | ensureRootIsScheduled(root);
204 | return;
205 | }
206 |
207 | // render 阶段
208 | const exitStatus = renderRoot(root, updateLane, false);
209 |
210 | // render 阶段结束后,进入 commit 阶段
211 | if (exitStatus == RootComplete) {
212 | // 创建根 Fiber 树的 Root Fiber
213 | const finishedWork = root.current.alternate;
214 | root.finishedWork = finishedWork;
215 | root.finishedLane = updateLane;
216 | workInProgressRenderLane = NoLane;
217 |
218 | // 提交阶段的入口函数
219 | commitRoot(root);
220 | } else if (__DEV__) {
221 | console.error('还未实现的同步更新结束状态');
222 | }
223 | }
224 |
225 | // 初始化 workInProgress 变量
226 | function prepareFreshStack(root: FiberRootNode, lane: Lane) {
227 | root.finishedLane = NoLane;
228 | root.finishedWork = null;
229 | workInProgress = createWorkInProgress(root.current, {});
230 | workInProgressRenderLane = lane;
231 | }
232 |
233 | // 深度优先遍历,向下递归子节点
234 | function workLoopSync() {
235 | while (workInProgress !== null) {
236 | performUnitOfWork(workInProgress);
237 | }
238 | }
239 |
240 | function workLoopConcurrent() {
241 | while (workInProgress !== null && !unstable_shouldYield) {
242 | performUnitOfWork(workInProgress);
243 | }
244 | }
245 |
246 | function performUnitOfWork(fiber: FiberNode) {
247 | // 比较并返回子 FiberNode
248 | const next = beginWork(fiber, workInProgressRenderLane);
249 | fiber.memoizedProps = fiber.pendingProps;
250 |
251 | if (next == null) {
252 | // 没有子节点,则遍历兄弟节点或父节点
253 | completeUnitOfWork(fiber);
254 | } else {
255 | // 有子节点,继续向下深度遍历
256 | workInProgress = next;
257 | }
258 | }
259 |
260 | // 深度优先遍历,向下递归子节点
261 | function completeUnitOfWork(fiber: FiberNode) {
262 | let node: FiberNode | null = fiber;
263 | do {
264 | const next = completeWork(node) as FiberNode | null;
265 | if (next !== null) {
266 | workInProgress = next;
267 | return;
268 | }
269 | // 有兄弟节点,则遍历兄弟节点
270 | const sibling = node.sibling;
271 | if (sibling !== null) {
272 | workInProgress = sibling;
273 | return;
274 | }
275 | // 否则向上返回,遍历父节点
276 | node = node.return;
277 | workInProgress = node;
278 | } while (node !== null);
279 | }
280 |
281 | function commitRoot(root: FiberRootNode) {
282 | const finishedWork = root.finishedWork;
283 | if (finishedWork === null) {
284 | return;
285 | }
286 |
287 | if (__DEV__) {
288 | console.warn('commit 阶段开始', finishedWork);
289 | }
290 |
291 | const lane = root.finishedLane;
292 | markRootFinished(root, lane);
293 |
294 | // 重置
295 | root.finishedWork = null;
296 | root.finishedLane = NoLane;
297 |
298 | const { flags, subtreeFlags } = finishedWork;
299 |
300 | // 判断 Fiber 树是否存在副作用
301 | if (
302 | (flags & PassiveMask) !== NoFlags ||
303 | (subtreeFlags & PassiveMask) !== NoFlags
304 | ) {
305 | if (!rootDoesHasPassiveEffects) {
306 | rootDoesHasPassiveEffects = true;
307 | // 调度副作用
308 | // 回调函数在 setTimeout 中以 NormalPriority 优先级被调度执行
309 | scheduleCallback(NormalPriority, () => {
310 | // 执行副作用
311 | flushPassiveEffects(root.pendingPassiveEffects);
312 | return;
313 | });
314 | }
315 | }
316 |
317 | // 判断是否存在需要执行的 commit 操作
318 | if (
319 | (flags & MutationMask) !== NoFlags ||
320 | (subtreeFlags & MutationMask) !== NoFlags
321 | ) {
322 | // TODO: BeforeMutation
323 |
324 | // Mutation
325 | commitMutationEffects(finishedWork, root);
326 | // Fiber 树切换,workInProgress 变成 current
327 | root.current = finishedWork;
328 |
329 | // TODO: Layout
330 | } else {
331 | root.current = finishedWork;
332 | }
333 |
334 | rootDoesHasPassiveEffects = false;
335 | ensureRootIsScheduled(root);
336 | }
337 |
338 | function flushPassiveEffects(
339 | pendingPassiveEffects: PendingPassiveEffects
340 | ): boolean {
341 | let didFlushPassiveEffect = false;
342 | // 先触发所有 unmount destroy
343 | pendingPassiveEffects.unmount.forEach((effect) => {
344 | didFlushPassiveEffect = true;
345 | commitHookEffectListUnmount(Passive, effect);
346 | });
347 | pendingPassiveEffects.unmount = [];
348 |
349 | // 再触发所有上次更新的 destroy
350 | pendingPassiveEffects.update.forEach((effect) => {
351 | didFlushPassiveEffect = true;
352 | commitHookEffectListDestory(Passive | HookHasEffect, effect);
353 | });
354 |
355 | // 再触发所有这次更新的 create
356 | pendingPassiveEffects.update.forEach((effect) => {
357 | didFlushPassiveEffect = true;
358 | commitHookEffectListCreate(Passive | HookHasEffect, effect);
359 | });
360 | pendingPassiveEffects.update = [];
361 |
362 | // 执行 useEffect 过程中可能触发新的更新
363 | // 再次调用 flushSyncCallback 处理这些更新的更新流程
364 | flushSyncCallback();
365 | return didFlushPassiveEffect;
366 | }
367 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/workTags.ts:
--------------------------------------------------------------------------------
1 | // FiberNode.tag 属性
2 |
3 | export type WorkTag =
4 | | typeof FunctionComponent
5 | | typeof HostRoot
6 | | typeof HostComponent
7 | | typeof HostText
8 | | typeof Fragment;
9 |
10 | export const FunctionComponent = 0;
11 | export const HostRoot = 3;
12 | export const HostComponent = 5;
13 | export const HostText = 6;
14 | export const Fragment = 7;
15 |
--------------------------------------------------------------------------------
/packages/react/__tests__/ReactElement-test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | * @emails react-core
8 | */
9 |
10 | 'use strict';
11 |
12 | let React;
13 | let ReactDOM;
14 | let ReactTestUtils;
15 |
16 | describe('ReactElement', () => {
17 | let ComponentFC;
18 | let originalSymbol;
19 |
20 | beforeEach(() => {
21 | jest.resetModules();
22 |
23 | // Delete the native Symbol if we have one to ensure we test the
24 | // unpolyfilled environment.
25 | originalSymbol = global.Symbol;
26 | global.Symbol = undefined;
27 |
28 | React = require('react');
29 | ReactDOM = require('react-dom');
30 | ReactTestUtils = require('react-dom/test-utils');
31 |
32 | // NOTE: We're explicitly not using JSX here. This is intended to test
33 | // classic JS without JSX.
34 | ComponentFC = () => {
35 | return React.createElement('div');
36 | };
37 | });
38 |
39 | afterEach(() => {
40 | global.Symbol = originalSymbol;
41 | });
42 |
43 | it('uses the fallback value when in an environment without Symbol', () => {
44 | expect(().$$typeof).toBe(0xeac7);
45 | });
46 |
47 | it('returns a complete element according to spec', () => {
48 | const element = React.createElement(ComponentFC);
49 | expect(element.type).toBe(ComponentFC);
50 | expect(element.key).toBe(null);
51 | expect(element.ref).toBe(null);
52 |
53 | expect(element.props).toEqual({});
54 | });
55 |
56 | it('allows a string to be passed as the type', () => {
57 | const element = React.createElement('div');
58 | expect(element.type).toBe('div');
59 | expect(element.key).toBe(null);
60 | expect(element.ref).toBe(null);
61 | expect(element.props).toEqual({});
62 | });
63 |
64 | it('returns an immutable element', () => {
65 | const element = React.createElement(ComponentFC);
66 | expect(() => (element.type = 'div')).not.toThrow();
67 | });
68 |
69 | it('does not reuse the original config object', () => {
70 | const config = { foo: 1 };
71 | const element = React.createElement(ComponentFC, config);
72 | expect(element.props.foo).toBe(1);
73 | config.foo = 2;
74 | expect(element.props.foo).toBe(1);
75 | });
76 |
77 | it('does not fail if config has no prototype', () => {
78 | const config = Object.create(null, { foo: { value: 1, enumerable: true } });
79 | const element = React.createElement(ComponentFC, config);
80 | expect(element.props.foo).toBe(1);
81 | });
82 |
83 | it('extracts key and ref from the config', () => {
84 | const element = React.createElement(ComponentFC, {
85 | key: '12',
86 | ref: '34',
87 | foo: '56'
88 | });
89 | expect(element.type).toBe(ComponentFC);
90 | expect(element.key).toBe('12');
91 | expect(element.ref).toBe('34');
92 | expect(element.props).toEqual({ foo: '56' });
93 | });
94 |
95 | it('extracts null key and ref', () => {
96 | const element = React.createElement(ComponentFC, {
97 | key: null,
98 | ref: null,
99 | foo: '12'
100 | });
101 | expect(element.type).toBe(ComponentFC);
102 | expect(element.key).toBe('null');
103 | expect(element.ref).toBe(null);
104 | expect(element.props).toEqual({ foo: '12' });
105 | });
106 |
107 | it('ignores undefined key and ref', () => {
108 | const props = {
109 | foo: '56',
110 | key: undefined,
111 | ref: undefined
112 | };
113 | const element = React.createElement(ComponentFC, props);
114 | expect(element.type).toBe(ComponentFC);
115 | expect(element.key).toBe(null);
116 | expect(element.ref).toBe(null);
117 | expect(element.props).toEqual({ foo: '56' });
118 | });
119 |
120 | it('ignores key and ref warning getters', () => {
121 | const elementA = React.createElement('div');
122 | const elementB = React.createElement('div', elementA.props);
123 | expect(elementB.key).toBe(null);
124 | expect(elementB.ref).toBe(null);
125 | });
126 |
127 | it('coerces the key to a string', () => {
128 | const element = React.createElement(ComponentFC, {
129 | key: 12,
130 | foo: '56'
131 | });
132 | expect(element.type).toBe(ComponentFC);
133 | expect(element.key).toBe('12');
134 | expect(element.ref).toBe(null);
135 | expect(element.props).toEqual({ foo: '56' });
136 | });
137 |
138 | it('merges an additional argument onto the children prop', () => {
139 | const a = 1;
140 | const element = React.createElement(
141 | ComponentFC,
142 | {
143 | children: 'text'
144 | },
145 | a
146 | );
147 | expect(element.props.children).toBe(a);
148 | });
149 |
150 | it('does not override children if no rest args are provided', () => {
151 | const element = React.createElement(ComponentFC, {
152 | children: 'text'
153 | });
154 | expect(element.props.children).toBe('text');
155 | });
156 |
157 | it('overrides children if null is provided as an argument', () => {
158 | const element = React.createElement(
159 | ComponentFC,
160 | {
161 | children: 'text'
162 | },
163 | null
164 | );
165 | expect(element.props.children).toBe(null);
166 | });
167 |
168 | it('merges rest arguments onto the children prop in an array', () => {
169 | const a = 1;
170 | const b = 2;
171 | const c = 3;
172 | const element = React.createElement(ComponentFC, null, a, b, c);
173 | expect(element.props.children).toEqual([1, 2, 3]);
174 | });
175 |
176 | // // NOTE: We're explicitly not using JSX here. This is intended to test
177 | // // classic JS without JSX.
178 | it('allows static methods to be called using the type property', () => {
179 | function StaticMethodComponent() {
180 | return React.createElement('div');
181 | }
182 | StaticMethodComponent.someStaticMethod = () => 'someReturnValue';
183 |
184 | const element = React.createElement(StaticMethodComponent);
185 | expect(element.type.someStaticMethod()).toBe('someReturnValue');
186 | });
187 |
188 | // // NOTE: We're explicitly not using JSX here. This is intended to test
189 | // // classic JS without JSX.
190 | it('identifies valid elements', () => {
191 | function Component() {
192 | return React.createElement('div');
193 | }
194 |
195 | expect(React.isValidElement(React.createElement('div'))).toEqual(true);
196 | expect(React.isValidElement(React.createElement(Component))).toEqual(true);
197 |
198 | expect(React.isValidElement(null)).toEqual(false);
199 | expect(React.isValidElement(true)).toEqual(false);
200 | expect(React.isValidElement({})).toEqual(false);
201 | expect(React.isValidElement('string')).toEqual(false);
202 | expect(React.isValidElement(Component)).toEqual(false);
203 | expect(React.isValidElement({ type: 'div', props: {} })).toEqual(false);
204 |
205 | const jsonElement = JSON.stringify(React.createElement('div'));
206 | expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
207 | });
208 |
209 | // // NOTE: We're explicitly not using JSX here. This is intended to test
210 | // // classic JS without JSX.
211 | it('is indistinguishable from a plain object', () => {
212 | const element = React.createElement('div', { className: 'foo' });
213 | const object = {};
214 | expect(element.constructor).toBe(object.constructor);
215 | });
216 |
217 | it('does not warn for NaN props', () => {
218 | function Test() {
219 | return ;
220 | }
221 |
222 | const test = ReactTestUtils.renderIntoDocument();
223 | expect(test.props.value).toBeNaN();
224 | });
225 |
226 | // // NOTE: We're explicitly not using JSX here. This is intended to test
227 | // // classic JS without JSX.
228 | it('identifies elements, but not JSON, if Symbols are supported', () => {
229 | // Rudimentary polyfill
230 | // @eslint-
231 | // Once all jest engines support Symbols natively we can swap this to test
232 | // WITH native Symbols by default.
233 | /*eslint-disable */
234 | const REACT_ELEMENT_TYPE = function () {}; // fake Symbol
235 | // eslint-disable-line no-use-before-define
236 | const OTHER_SYMBOL = function () {}; // another fake Symbol
237 | /*eslint-enable */
238 | global.Symbol = function (name) {
239 | return OTHER_SYMBOL;
240 | };
241 | global.Symbol.for = function (key) {
242 | if (key === 'react.element') {
243 | return REACT_ELEMENT_TYPE;
244 | }
245 | return OTHER_SYMBOL;
246 | };
247 |
248 | jest.resetModules();
249 |
250 | React = require('react');
251 |
252 | function Component() {
253 | return React.createElement('div');
254 | }
255 |
256 | expect(React.isValidElement(React.createElement('div'))).toEqual(true);
257 | expect(React.isValidElement(React.createElement(Component))).toEqual(true);
258 |
259 | expect(React.isValidElement(null)).toEqual(false);
260 | expect(React.isValidElement(true)).toEqual(false);
261 | expect(React.isValidElement({})).toEqual(false);
262 | expect(React.isValidElement('string')).toEqual(false);
263 |
264 | expect(React.isValidElement(Component)).toEqual(false);
265 | expect(React.isValidElement({ type: 'div', props: {} })).toEqual(false);
266 |
267 | const jsonElement = JSON.stringify(React.createElement('div'));
268 | expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
269 | });
270 | });
--------------------------------------------------------------------------------
/packages/react/index.ts:
--------------------------------------------------------------------------------
1 | // React
2 | import currentDispatcher, {
3 | Dispatcher,
4 | resolveDispatcher
5 | } from './src/currentDispatcher';
6 | import {
7 | createElement as createElementFn,
8 | isValidElement as isValidElementFn
9 | } from './src/jsx';
10 |
11 | export const useState: Dispatcher['useState'] = (initialState) => {
12 | const dispatcher = resolveDispatcher();
13 | return dispatcher.useState(initialState);
14 | };
15 |
16 | export const useEffect: Dispatcher['useEffect'] = (creact, deps) => {
17 | const dispatcher = resolveDispatcher();
18 | return dispatcher.useEffect(creact, deps);
19 | };
20 |
21 | // 内部数据共享层
22 | export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
23 | currentDispatcher
24 | };
25 |
26 | export const version = '1.0.0';
27 | export const isValidElement = isValidElementFn;
28 | export const createElement = createElementFn;
29 |
--------------------------------------------------------------------------------
/packages/react/jsx-dev-runtime.ts:
--------------------------------------------------------------------------------
1 | // React jsxDEV
2 | export { jsxDEV, Fragment } from './src/jsx';
3 |
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "version": "1.0.0",
4 | "description": "react common functions",
5 | "module": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace: *"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react/src/currentDispatcher.ts:
--------------------------------------------------------------------------------
1 | import { Action } from 'shared/ReactTypes';
2 |
3 | // const [data, setData] = useState(0);
4 | // or
5 | // const [data, setData] = useState(0data) => data + 1);
6 | export interface Dispatcher {
7 | useState: (initialState: (() => S) | S) => [S, Dispatch];
8 | useEffect: (callback: () => void | void, deps: any[] | void) => void;
9 | }
10 |
11 | export type Dispatch = (action: Action) => void;
12 |
13 | const currentDispatcher: { current: Dispatcher | null } = {
14 | current: null
15 | };
16 |
17 | export const resolveDispatcher = (): Dispatcher => {
18 | const dispatcher = currentDispatcher.current;
19 | if (dispatcher == null) {
20 | throw new Error('Hooks 只能在函数组件中执行');
21 | }
22 | return dispatcher;
23 | };
24 |
25 | export default currentDispatcher;
26 |
--------------------------------------------------------------------------------
/packages/react/src/jsx.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
3 | import {
4 | Type,
5 | Ref,
6 | Key,
7 | Props,
8 | ReactElementType,
9 | ElementType
10 | } from 'shared/ReactTypes';
11 |
12 | const ReactElement = function (
13 | type: Type,
14 | key: Key,
15 | ref: Ref,
16 | props: Props
17 | ): ReactElementType {
18 | const element = {
19 | $$typeof: REACT_ELEMENT_TYPE,
20 | type,
21 | key,
22 | ref,
23 | props,
24 | __mark: 'erxiao'
25 | };
26 | return element;
27 | };
28 |
29 | export function isValidElement(object: any): object is ReactElementType {
30 | return (
31 | typeof object === 'object' &&
32 | object !== null &&
33 | object.$$typeof === REACT_ELEMENT_TYPE
34 | );
35 | }
36 |
37 | export const Fragment = REACT_FRAGMENT_TYPE;
38 |
39 | export const jsx = (type: ElementType, config: any, ...children: any) => {
40 | let key: Key = null;
41 | let ref: Ref = null;
42 | const props: Props = {};
43 | for (const prop in config) {
44 | const val = config[prop];
45 | if (prop === 'key') {
46 | if (val !== undefined) {
47 | key = '' + val;
48 | }
49 | continue;
50 | }
51 | if (prop === 'ref') {
52 | if (val !== undefined) {
53 | ref = val;
54 | }
55 | continue;
56 | }
57 | if ({}.hasOwnProperty.call(config, prop)) {
58 | props[prop] = val;
59 | }
60 | }
61 | const childrenLength = children.length;
62 | if (childrenLength) {
63 | if (childrenLength === 1) {
64 | props.children = children[0];
65 | } else {
66 | props.children = children;
67 | }
68 | }
69 | return ReactElement(type, key, ref, props);
70 | };
71 |
72 | export const jsxDEV = (type: ElementType, config: any) => {
73 | let key: Key = null;
74 | let ref: Ref = null;
75 | const props: Props = {};
76 | for (const prop in config) {
77 | const val = config[prop];
78 | if (prop === 'key') {
79 | if (val !== undefined) {
80 | key = '' + val;
81 | }
82 | continue;
83 | }
84 | if (prop === 'ref') {
85 | if (val !== undefined) {
86 | ref = val;
87 | }
88 | continue;
89 | }
90 | if ({}.hasOwnProperty.call(config, prop)) {
91 | props[prop] = val;
92 | }
93 | }
94 |
95 | return ReactElement(type, key, ref, props);
96 | };
97 |
98 | export const createElement = jsx;
99 |
--------------------------------------------------------------------------------
/packages/shared/ReactSymbols.ts:
--------------------------------------------------------------------------------
1 | const supportSymbol = typeof Symbol === 'function' && Symbol.for;
2 |
3 | // ReactElement.type 属性
4 |
5 | // 表示普通的 React 元素,即通过 JSX 创建的组件或 DOM 元素
6 | export const REACT_ELEMENT_TYPE = supportSymbol
7 | ? Symbol.for('react.element')
8 | : 0xeac7;
9 |
10 | // 表示 Fragment 组件,即 或短语法 <>> 创建的 Fragment
11 | export const REACT_FRAGMENT_TYPE = supportSymbol
12 | ? Symbol.for('react.fragment')
13 | : 0xeacb;
14 |
--------------------------------------------------------------------------------
/packages/shared/ReactTypes.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | export type Type = any;
3 | export type Key = any;
4 | export type Props = any;
5 | export type Ref = any;
6 | export type ElementType = any;
7 |
8 | export interface ReactElementType {
9 | $$typeof: symbol | number;
10 | key: Key;
11 | props: Props;
12 | ref: Ref;
13 | type: ElementType;
14 | __mark: string;
15 | }
16 |
17 | export type Action = State | ((prevState: State) => State);
18 |
--------------------------------------------------------------------------------
/packages/shared/internals.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // 为了将 react-reconciler 和 react 解耦,在 shared 中转,方便 react-reconciler 使用
4 | const internals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
5 |
6 | export default internals;
7 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shared",
3 | "version": "1.0.0",
4 | "description": "shared hepler functions and symbols",
5 | "keywords": [],
6 | "author": "",
7 | "license": "ISC"
8 | }
9 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
--------------------------------------------------------------------------------
/scripts/jest/jest.config.js:
--------------------------------------------------------------------------------
1 | const { defaults } = require('jest-config');
2 |
3 | module.exports = {
4 | ...defaults,
5 | rootDir: process.cwd(),
6 | // 寻找测试用例忽略的文件夹
7 | modulePathIgnorePatterns: ['/.history'],
8 | // 依赖包的解析地址
9 | moduleDirectories: [
10 | // React 和 ReactDOM 包的地址
11 | 'dist/node_modules',
12 | // 第三方依赖的地址
13 | ...defaults.moduleDirectories
14 | ],
15 | testEnvironment: 'jsdom',
16 | moduleNameMapper: {
17 | '^scheduler$': '/node_modules/scheduler/unstable_mock.js'
18 | },
19 | fakeTimers: {
20 | enableGlobally: true,
21 | legacyFakeTimers: true
22 | },
23 | setupFilesAfterEnv: ['./scripts/jest/setupJest.js']
24 | }
25 |
--------------------------------------------------------------------------------
/scripts/jest/reactTestMatchers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const JestReact = require('jest-react');
4 | const SchedulerMatchers = require('./schedulerTestMatchers');
5 |
6 | function captureAssertion(fn) {
7 | // Trick to use a Jest matcher inside another Jest matcher. `fn` contains an
8 | // assertion; if it throws, we capture the error and return it, so the stack
9 | // trace presented to the user points to the original assertion in the
10 | // test file.
11 | try {
12 | fn();
13 | } catch (error) {
14 | return {
15 | pass: false,
16 | message: () => error.message
17 | };
18 | }
19 | return { pass: true };
20 | }
21 |
22 | function assertYieldsWereCleared(Scheduler) {
23 | const actualYields = Scheduler.unstable_clearYields();
24 | if (actualYields.length !== 0) {
25 | throw new Error(
26 | 'Log of yielded values is not empty. ' +
27 | 'Call expect(Scheduler).toHaveYielded(...) first.'
28 | );
29 | }
30 | }
31 |
32 | function toMatchRenderedOutput(ReactNoop, expectedJSX) {
33 | if (typeof ReactNoop.getChildrenAsJSX === 'function') {
34 | const Scheduler = ReactNoop._Scheduler;
35 | assertYieldsWereCleared(Scheduler);
36 | return captureAssertion(() => {
37 | expect(ReactNoop.getChildrenAsJSX()).toEqual(expectedJSX);
38 | });
39 | }
40 | return JestReact.unstable_toMatchRenderedOutput(ReactNoop, expectedJSX);
41 | }
42 |
43 | module.exports = {
44 | ...SchedulerMatchers,
45 | toMatchRenderedOutput
46 | };
--------------------------------------------------------------------------------
/scripts/jest/schedulerTestMatchers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function captureAssertion(fn) {
4 | // Trick to use a Jest matcher inside another Jest matcher. `fn` contains an
5 | // assertion; if it throws, we capture the error and return it, so the stack
6 | // trace presented to the user points to the original assertion in the
7 | // test file.
8 | try {
9 | fn();
10 | } catch (error) {
11 | return {
12 | pass: false,
13 | message: () => error.message
14 | };
15 | }
16 | return { pass: true };
17 | }
18 |
19 | function assertYieldsWereCleared(Scheduler) {
20 | const actualYields = Scheduler.unstable_clearYields();
21 | if (actualYields.length !== 0) {
22 | throw new Error(
23 | 'Log of yielded values is not empty. ' +
24 | 'Call expect(Scheduler).toHaveYielded(...) first.'
25 | );
26 | }
27 | }
28 |
29 | function toFlushAndYield(Scheduler, expectedYields) {
30 | assertYieldsWereCleared(Scheduler);
31 | Scheduler.unstable_flushAllWithoutAsserting();
32 | const actualYields = Scheduler.unstable_clearYields();
33 | return captureAssertion(() => {
34 | expect(actualYields).toEqual(expectedYields);
35 | });
36 | }
37 |
38 | function toFlushAndYieldThrough(Scheduler, expectedYields) {
39 | assertYieldsWereCleared(Scheduler);
40 | Scheduler.unstable_flushNumberOfYields(expectedYields.length);
41 | const actualYields = Scheduler.unstable_clearYields();
42 | return captureAssertion(() => {
43 | expect(actualYields).toEqual(expectedYields);
44 | });
45 | }
46 |
47 | function toFlushUntilNextPaint(Scheduler, expectedYields) {
48 | assertYieldsWereCleared(Scheduler);
49 | Scheduler.unstable_flushUntilNextPaint();
50 | const actualYields = Scheduler.unstable_clearYields();
51 | return captureAssertion(() => {
52 | expect(actualYields).toEqual(expectedYields);
53 | });
54 | }
55 |
56 | function toFlushWithoutYielding(Scheduler) {
57 | return toFlushAndYield(Scheduler, []);
58 | }
59 |
60 | function toFlushExpired(Scheduler, expectedYields) {
61 | assertYieldsWereCleared(Scheduler);
62 | Scheduler.unstable_flushExpired();
63 | const actualYields = Scheduler.unstable_clearYields();
64 | return captureAssertion(() => {
65 | expect(actualYields).toEqual(expectedYields);
66 | });
67 | }
68 |
69 | function toHaveYielded(Scheduler, expectedYields) {
70 | return captureAssertion(() => {
71 | const actualYields = Scheduler.unstable_clearYields();
72 | expect(actualYields).toEqual(expectedYields);
73 | });
74 | }
75 |
76 | function toFlushAndThrow(Scheduler, ...rest) {
77 | assertYieldsWereCleared(Scheduler);
78 | return captureAssertion(() => {
79 | expect(() => {
80 | Scheduler.unstable_flushAllWithoutAsserting();
81 | }).toThrow(...rest);
82 | });
83 | }
84 |
85 | module.exports = {
86 | toFlushAndYield,
87 | toFlushAndYieldThrough,
88 | toFlushUntilNextPaint,
89 | toFlushWithoutYielding,
90 | toFlushExpired,
91 | toHaveYielded,
92 | toFlushAndThrow
93 | };
--------------------------------------------------------------------------------
/scripts/jest/setupJest.js:
--------------------------------------------------------------------------------
1 | expect.extend({
2 | ...require('./reactTestMatchers')
3 | });
--------------------------------------------------------------------------------
/scripts/rollup/dev.config.js:
--------------------------------------------------------------------------------
1 | import reactConfig from './react.config';
2 | import reactDomConfig from './react-dom.config';
3 | import reactNoopRendererConfig from './react-noop-renderer.config';
4 |
5 | export default () => {
6 | return [...reactConfig, ...reactDomConfig, ...reactNoopRendererConfig];
7 | };
--------------------------------------------------------------------------------
/scripts/rollup/react-dom.config.js:
--------------------------------------------------------------------------------
1 | import { getPackageJSON, resolvePkgPath, getBaseRollupPlugins } from './utils';
2 | import generatePackageJson from 'rollup-plugin-generate-package-json';
3 | import alias from '@rollup/plugin-alias';
4 |
5 | const { name, module, peerDependencies } = getPackageJSON('react-dom');
6 | // react-dom 包的路径
7 | const pkgPath = resolvePkgPath(name);
8 | // react-dom 包的产物路径
9 | const pkgDistPath = resolvePkgPath(name, true);
10 |
11 | export default [
12 | // react-dom
13 | {
14 | input: `${pkgPath}/${module}`,
15 | output: [
16 | {
17 | file: `${pkgDistPath}/index.js`,
18 | name: 'ReactDOM',
19 | format: 'umd'
20 | },
21 | {
22 | file: `${pkgDistPath}/client.js`,
23 | name: 'client',
24 | format: 'umd'
25 | }
26 | ],
27 | external: [...Object.keys(peerDependencies), 'scheduler'],
28 | plugins: [
29 | ...getBaseRollupPlugins(),
30 | // webpack resolve alias
31 | alias({
32 | entries: {
33 | hostConfig: `${pkgPath}/src/hostConfig.ts`
34 | }
35 | }),
36 | generatePackageJson({
37 | inputFolder: pkgPath,
38 | outputFolder: pkgDistPath,
39 | baseContents: ({ name, description, version }) => ({
40 | name,
41 | description,
42 | version,
43 | peerDependencies: {
44 | react: version
45 | },
46 | main: 'index.js'
47 | })
48 | })
49 | ]
50 | },
51 | // react-test-utils
52 | {
53 | input: `${pkgPath}/test-utils.ts`,
54 | output: [
55 | {
56 | file: `${pkgDistPath}/test-utils.js`,
57 | name: 'testUtils',
58 | format: 'umd'
59 | }
60 | ],
61 | external: ['react-dom', 'react'],
62 | plugins: getBaseRollupPlugins()
63 | }
64 | ];
--------------------------------------------------------------------------------
/scripts/rollup/react-noop-renderer.config.js:
--------------------------------------------------------------------------------
1 | import { getPackageJSON, resolvePkgPath, getBaseRollupPlugins } from './utils';
2 | import generatePackageJson from 'rollup-plugin-generate-package-json';
3 | import alias from '@rollup/plugin-alias';
4 |
5 | const { name, module, peerDependencies } = getPackageJSON('react-noop-renderer');
6 | // react-noop-renderer 包的路径
7 | const pkgPath = resolvePkgPath(name);
8 | // react-noop-renderer 包的产物路径
9 | const pkgDistPath = resolvePkgPath(name, true);
10 |
11 | export default [
12 | // react-noop-renderer
13 | {
14 | input: `${pkgPath}/${module}`,
15 | output: [
16 | {
17 | file: `${pkgDistPath}/index.js`,
18 | name: 'ReactNoopRenderer',
19 | format: 'umd'
20 | }
21 | ],
22 | external: [...Object.keys(peerDependencies), 'scheduler'],
23 | plugins: [
24 | ...getBaseRollupPlugins({
25 | typescript: {
26 | exclude: ['./packages/react-dom/**/*'],
27 | tsconfigOverride: {
28 | compilerOptions: {
29 | paths: {
30 | hostConfig: [`./${name}/src/hostConfig.ts`]
31 | }
32 | }
33 | }
34 | }
35 | }),
36 | // webpack resolve alias
37 | alias({
38 | entries: {
39 | hostConfig: `${pkgPath}/src/hostConfig.ts`
40 | }
41 | }),
42 | generatePackageJson({
43 | inputFolder: pkgPath,
44 | outputFolder: pkgDistPath,
45 | baseContents: ({ name, description, version }) => ({
46 | name,
47 | description,
48 | version,
49 | peerDependencies: {
50 | react: version
51 | },
52 | main: 'index.js'
53 | })
54 | })
55 | ]
56 | }
57 | ];
--------------------------------------------------------------------------------
/scripts/rollup/react.config.js:
--------------------------------------------------------------------------------
1 | import { getPackageJSON, resolvePkgPath, getBaseRollupPlugins } from './utils';
2 | import generatePackageJson from 'rollup-plugin-generate-package-json';
3 |
4 | const { name, module } = getPackageJSON('react');
5 | // react 包的路径
6 | const pkgPath = resolvePkgPath(name);
7 | // react 包的产物路径
8 | const pkgDistPath = resolvePkgPath(name, true);
9 |
10 | export default [
11 | // react
12 | {
13 | input: `${pkgPath}/${module}`,
14 | output: {
15 | file: `${pkgDistPath}/index.js`,
16 | name: 'React',
17 | format: 'umd'
18 | },
19 | plugins: [
20 | ...getBaseRollupPlugins(),
21 | generatePackageJson({
22 | inputFolder: pkgPath,
23 | outputFolder: pkgDistPath,
24 | baseContents: ({ name, description, version }) => ({
25 | name,
26 | description,
27 | version,
28 | main: 'index.js'
29 | })
30 | })
31 | ]
32 | },
33 | // jsx-runtime
34 | {
35 | input: `${pkgPath}/src/jsx.ts`,
36 | output: [
37 | // jsx-runtime
38 | {
39 | file: `${pkgDistPath}/jsx-runtime.js`,
40 | name: 'jsx-runtime',
41 | format: 'umd'
42 | },
43 | // jsx-dev-runtime
44 | {
45 | file: `${pkgDistPath}/jsx-dev-runtime.js`,
46 | name: 'jsx-dev-runtime',
47 | format: 'umd'
48 | }
49 | ],
50 | plugins: getBaseRollupPlugins()
51 | }
52 | ];
53 |
--------------------------------------------------------------------------------
/scripts/rollup/utils.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs';
3 | import ts from 'rollup-plugin-typescript2';
4 | import cjs from '@rollup/plugin-commonjs';
5 | import replace from '@rollup/plugin-replace';
6 |
7 | const pkgPath = path.resolve(__dirname, '../../packages');
8 | const distPath = path.resolve(__dirname, '../../dist/node_modules');
9 |
10 | export function resolvePkgPath(pkgName, isDist) {
11 | if (isDist) {
12 | return `${distPath}/${pkgName}`;
13 | }
14 | return `${pkgPath}/${pkgName}`;
15 | }
16 |
17 | export function getPackageJSON(pkgName) {
18 | const path = `${resolvePkgPath(pkgName)}/package.json`;
19 | const str = fs.readFileSync(path, { encoding: 'utf-8' });
20 | return JSON.parse(str);
21 | }
22 |
23 | export function getBaseRollupPlugins({
24 | alias = {
25 | __DEV__: true,
26 | preventAssignment: true
27 | },
28 | typescript = {}
29 | } = {}) {
30 | return [replace(alias), cjs(), ts(typescript)];
31 | }
--------------------------------------------------------------------------------
/scripts/vite/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import replace from '@rollup/plugin-replace'
4 | import { resolvePkgPath } from '../rollup/utils'
5 | import path from 'path'
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | plugins: [react(), replace({ __DEV__: true, preventAssignment: true })],
10 | resolve: {
11 | alias: [{
12 | find: 'react',
13 | replacement: resolvePkgPath('react')
14 | },
15 | {
16 | find: 'react-dom',
17 | replacement: resolvePkgPath('react-dom')
18 | },
19 | {
20 | find: 'react-noop-renderer',
21 | replacement: resolvePkgPath('react-noop-renderer')
22 | },
23 | {
24 | find: 'hostConfig',
25 | // replacement: path.resolve(resolvePkgPath('react-dom'), './src/hostConfig.ts')
26 | replacement: path.resolve(resolvePkgPath('react-dom'), './src/hostConfig.ts')
27 | }
28 | ]
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": true,
3 | "include": ["./packages/**/*"],
4 | "compilerOptions": {
5 | "target": "ESNext",
6 | "useDefineForClassFields": true,
7 | "module": "ESNext",
8 | "lib": ["ESNext", "DOM"],
9 | "moduleResolution": "Node",
10 | "strict": true,
11 | "sourceMap": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "esModuleInterop": true,
15 | "noEmit": true,
16 | "noUnusedLocals": false,
17 | "noUnusedParameters": false,
18 | "noImplicitReturns": false,
19 | "skipLibCheck": true,
20 | "baseUrl": "./packages",
21 | "paths": {
22 | "hostConfig": ["./react-dom/src/hostConfig.ts"]
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------