├── .DS_Store
├── .commitlintrc.js
├── .eslintrc.json
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierrc.json
├── .vscode
└── settings.json
├── README.md
├── babel.config.js
├── demos
├── context
│ ├── index.html
│ ├── main.tsx
│ └── vite-env.d.ts
├── performance
│ ├── Context.tsx
│ ├── Hook.tsx
│ ├── Principle_demo1.tsx
│ ├── Principle_demo2.tsx
│ ├── Simple.tsx
│ ├── index.html
│ ├── main.tsx
│ ├── memo.tsx
│ ├── useMemo.tsx
│ └── vite-env.d.ts
├── ref
│ ├── index.html
│ ├── main.tsx
│ └── vite-env.d.ts
├── suspense-lazy
│ ├── Cpn.tsx
│ ├── index.html
│ └── main.tsx
├── suspense-use
│ ├── Cpn.tsx
│ ├── index.html
│ ├── main.tsx
│ └── vite-env.d.ts
├── test-fc
│ ├── index.html
│ ├── main.ts
│ ├── main.tsx
│ └── style.css
└── transition
│ ├── AboutTab.tsx
│ ├── ContactTab.tsx
│ ├── PostsTab.tsx
│ ├── TabButton.tsx
│ ├── index.html
│ ├── main.tsx
│ └── style.css
├── package.json
├── packages
├── react-dom
│ ├── client.ts
│ ├── index.ts
│ ├── node_modules
│ │ ├── react-reconciler
│ │ └── shared
│ ├── 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
│ ├── node_modules
│ │ └── shared
│ ├── package.json
│ └── src
│ │ ├── __tests__
│ │ └── ReactEffectOrdering-test.js
│ │ ├── beginWork.ts
│ │ ├── childFibers.ts
│ │ ├── commitWork.ts
│ │ ├── completeWork.ts
│ │ ├── fiber.ts
│ │ ├── fiberContext.ts
│ │ ├── fiberFlags.ts
│ │ ├── fiberHooks.ts
│ │ ├── fiberLanes.ts
│ │ ├── fiberReconciler.ts
│ │ ├── fiberThrow.ts
│ │ ├── fiberUnwindWork.ts
│ │ ├── hookEffectTags.ts
│ │ ├── reconciler.d.ts
│ │ ├── suspenseContext.ts
│ │ ├── syncTaskQueue.ts
│ │ ├── thenable.ts
│ │ ├── updateQueue.ts
│ │ ├── workLoop.ts
│ │ └── workTags.ts
├── react
│ ├── index.ts
│ ├── jsx-dev-runtime.ts
│ ├── node_modules
│ │ └── shared
│ ├── package.json
│ └── src
│ │ ├── __tests__
│ │ └── ReactElement-test.js
│ │ ├── context.ts
│ │ ├── currentBatchConfig.ts
│ │ ├── currentDispatcher.ts
│ │ ├── jsx.ts
│ │ ├── lazy.ts
│ │ └── memo.ts
└── shared
│ ├── ReactSymbols.ts
│ ├── ReactTypes.ts
│ ├── internals.ts
│ ├── package.json
│ └── shallowEquals.ts
├── 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
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BetaSu/big-react/0bdc6d4efea946a3ca608d70b9fd48852f0ddddb/.DS_Store
--------------------------------------------------------------------------------
/.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 | "no-unused-vars": "off"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .history
3 | dist
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Big-React
2 |
3 | 从零实现 React v18 的核心功能,特点如下:
4 |
5 | - 👬 与 React 源码最接近的实现
6 | - 💪 功能完备,当前可跑通官方测试用例数量:34
7 | - 🚶 按`Git Tag`划分迭代步骤,记录从 0 实现的每个功能
8 |
9 | 如果想跟着我学习「如何从 0 到 1 实现 React18」,可以[点击这里](https://qux.xet.tech/s/2wiFh1)
10 |
11 | ## TODO List
12 |
13 | ### 工程类需求
14 |
15 | | 类型 | 内容 | 完成情况 | 在哪个版本实现的 |
16 | | ---- | ---------------------------------- | -------- | ------------------------------------------------- |
17 | | 架构 | monorepo(pnpm 实现) | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
18 | | 规范 | eslint | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
19 | | 规范 | prettier | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
20 | | 规范 | commitlint + husky | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
21 | | 规范 | lint-staged | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
22 | | 规范 | tsc | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
23 | | 测试 | jest 环境搭建 | ✅ | [v4](https://github.com/BetaSu/big-react/tree/v4) |
24 | | 规范 | tsc | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
25 | | 构建 | babel 配置 | ✅ | [v4](https://github.com/BetaSu/big-react/tree/v4) |
26 | | 构建 | Dev 环境包的构建 | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
27 | | 构建 | Prod 环境包的构建 | ⬜️ | |
28 | | 部署 | Github Action 执行 lint 与 test | ⬜️ | |
29 | | 部署 | Github Action 根据 tag 发布 npm 包 | ⬜️ | |
30 |
31 | ### 框架需求
32 |
33 | | 类型 | 内容 | 完成情况 | 在哪个版本实现的 |
34 | | ---------- | -------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- |
35 | | React | JSX 转换 | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
36 | | React | React.isValidElement | ✅ | [v4](https://github.com/BetaSu/big-react/tree/v4) |
37 | | ReactDOM | 浏览器环境 DOM 的插入 | ✅ | [v2](https://github.com/BetaSu/big-react/tree/v2) |
38 | | ReactDOM | 浏览器环境 DOM 的移动 | ✅ | [v7](https://github.com/BetaSu/big-react/tree/v7) |
39 | | ReactDOM | 浏览器环境 DOM 的属性变化 | ⬜️ | |
40 | | ReactDOM | 浏览器环境 DOM 的删除 | ✅ | [v5](https://github.com/BetaSu/big-react/tree/v5) |
41 | | ReactDOM | ReactTestUtils | ✅ | [v4](https://github.com/BetaSu/big-react/tree/v4) |
42 | | ReactNoop | ReactNoop Renderer | ✅ | [v10](https://github.com/BetaSu/big-react/tree/v10) |
43 | | Reconciler | Fiber 架构 | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
44 | | Reconciler | 事件模型 | ✅ | [v6](https://github.com/BetaSu/big-react/tree/v6) |
45 | | Reconciler | onClick 事件支持 | ✅ | [v6](https://github.com/BetaSu/big-react/tree/v6) |
46 | | Reconciler | input 元素 onChange 事件支持 | ⬜️ | |
47 | | Reconciler | Lane 模型 | ✅ | [v8](https://github.com/BetaSu/big-react/tree/v8) |
48 | | Reconciler | 基础 Update 机制 | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
49 | | Reconciler | 带优先级的 Update 机制 | ✅ | [v8](https://github.com/BetaSu/big-react/tree/v8) |
50 | | Reconciler | 插入单节点的 mount 流程 | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
51 | | Reconciler | 插入多节点的 mount 流程 | ✅ | [v7](https://github.com/BetaSu/big-react/tree/v7) |
52 | | Reconciler | 插入单节点的 reconcile 流程 | ✅ | [v5](https://github.com/BetaSu/big-react/tree/v5) |
53 | | Reconciler | 插入多节点的 reconcile 流程 | ✅ | [v7](https://github.com/BetaSu/big-react/tree/v7) |
54 | | Reconciler | 删除节点的 reconcile 流程 | ✅ | [v5](https://github.com/BetaSu/big-react/tree/v5) |
55 | | Reconciler | HostText 类型支持 | ✅ | [v2](https://github.com/BetaSu/big-react/tree/v2) |
56 | | Reconciler | HostComponent 类型支持 | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
57 | | Reconciler | HostRoot 类型支持 | ✅ | [v1](https://github.com/BetaSu/big-react/tree/v1) |
58 | | Reconciler | FunctionComponent 类型支持 | ✅ | [v3](https://github.com/BetaSu/big-react/tree/v3) |
59 | | React | Hooks 架构 mount 时实现 | ✅ | [v3](https://github.com/BetaSu/big-react/tree/v3) |
60 | | React | Hooks 架构 update 时实现 | ✅ | [v5](https://github.com/BetaSu/big-react/tree/v5) |
61 | | Reconciler | useState 实现 | ✅ | [v3](https://github.com/BetaSu/big-react/tree/v3) |
62 | | Reconciler | useEffect 实现 | ✅ | [v9](https://github.com/BetaSu/big-react/tree/v9) |
63 | | Reconciler | useRef 实现 | ✅ | [ref](https://github.com/BetaSu/big-react/commit/41e1b4aff567804a872a19674c1a6efcce07abf6) |
64 | | Reconciler | Legacy 调度流程(包含 batchedUpdates) | ✅ | [v8](https://github.com/BetaSu/big-react/tree/v8) |
65 | | Reconciler | Concurrent 调度流程 | ✅ | [v11](https://github.com/BetaSu/big-react/tree/v11) |
66 | | Reconciler | useTransition 实现 | ✅ | [useTransition](https://github.com/BetaSu/big-react/commit/e0754721e1d11465bafc25c0360604a536e16d60) |
67 | | Reconciler | useContext 及 context 流程实现 | ✅ | [context 实现](https://github.com/BetaSu/big-react/commit/18d25044415b8d3e558d7027e23607d01c03f40a) |
68 | | Reconciler | unwind 流程 | ✅ | [unwind 流程](https://github.com/BetaSu/big-react/commit/18d25044415b8d3e558d7027e23607d01c03f40a) |
69 | | Reconciler | Suspense 组件实现 | ✅ | [Suspense](https://github.com/BetaSu/big-react/commit/306bcf975bca29c19b4d5423cdf01ed7af131c32) |
70 | | Reconciler | Offscreen 组件实现 | ✅ | [Offscreen](https://github.com/BetaSu/big-react/commit/306bcf975bca29c19b4d5423cdf01ed7af131c32) |
71 | | Reconciler | use hook 实现 | ✅ | [use hook](https://github.com/BetaSu/big-react/commit/306bcf975bca29c19b4d5423cdf01ed7af131c32) |
72 | | React | React.lazy 实现 | ✅ | [Lazy 实现](https://github.com/BetaSu/big-react/pull/49/files) 由[L-Qun](https://github.com/L-Qun)完成 |
73 | | React | React.memo 实现 | ✅ | [React.memo 实现](https://github.com/BetaSu/big-react/commit/bb1cedd2a4e6ba99562d28fdaa38e52d8da70525) |
74 | | Reconciler | bailout性能优化策略 | ✅ | [bailout 实现](https://github.com/BetaSu/big-react/commit/bb1cedd2a4e6ba99562d28fdaa38e52d8da70525) |
75 | | Reconciler | eagerState性能优化策略 | ✅ | [eagerState 实现](https://github.com/BetaSu/big-react/commit/bb1cedd2a4e6ba99562d28fdaa38e52d8da70525) |
76 | | Reconciler | useMemo 实现 | ✅ | [useMemo 实现](https://github.com/BetaSu/big-react/commit/bb1cedd2a4e6ba99562d28fdaa38e52d8da70525) |
77 | | Reconciler | useCallback 实现 | ✅ | [useCallback 实现](https://github.com/BetaSu/big-react/commit/bb1cedd2a4e6ba99562d28fdaa38e52d8da70525) |
78 | | Reconciler | context兼容bailout策略 | ✅ | [context兼容](https://github.com/BetaSu/big-react/commit/bb1cedd2a4e6ba99562d28fdaa38e52d8da70525) |
79 |
80 |
81 |
82 |
83 | ## 调试
84 |
85 | 提供 3 种调试方式:
86 |
87 | 1. 实时调试
88 |
89 | 执行`pnpm demo`会运行项目`demos`目录下的示例项目(默认项目是针对[v9](https://github.com/BetaSu/big-react/tree/v9)的调试项目)
90 |
91 | 这种方式的好处是:
92 |
93 | - 控制台会打印各个主要步骤的执行信息,可以直观看到执行流程
94 |
95 | - 热更新(包括示例代码和源码代码)
96 |
97 | 2. pnpm link
98 |
99 | 通过`CRA`或`Vite`起一个`React`测试项目后,在本项目执行`pnpm run build:dev`打包`react`与`react-dom`,在测试项目中通过`pnpm link`将项目依赖的`react`与`react-dom`替换为我们打包的`react`与`react-dom`
100 |
101 | 这种方式的好处是:最贴合项目中实际使用`React`的情况
102 |
103 | 3. 跑`React`官方的测试用例
104 |
105 | 执行`pnpm test`跑官方的测试用例,用例中引用的是执行`pnpm run build:dev`打包的`react`与`react-dom`
106 |
107 | 这种方式的好处是:可以从官方用例的角度观察框架实现的细节、各种边界情况
108 |
109 | ## 更新日志
110 |
111 | ### [v11](https://github.com/BetaSu/big-react/tree/v11)
112 |
113 | 实现了并发更新,通过修改 packages/react-dom/src/SyntheticEvent.ts 中的 eventTypeToEventPriority 方法下的 click 对应优先级,
114 | 可以观察同步更新(SyncLane)与其他优先级下的点击事件中触发更新的区别(是否会开启时间切片)。包括如下功能:
115 |
116 | - Concurrent 调度流程
117 |
118 | ### [v10](https://github.com/BetaSu/big-react/tree/v10)
119 |
120 | 这一版的改动比较大,为了实现 React-Noop-Renderer,对 React-Reconciler 与 rollup 配置做了一些调整,使 React-Reconciler 更通用(可以对接不同宿主环境)。包括如下功能:
121 |
122 | - 实现 React-Noop-Renderer,可以脱离 ReactDOM 更好的测试 Recocniler 逻辑
123 |
124 | - 对 rollup 配置做了改动,以配合 React-Reconciler 更好对接不同宿主环境
125 |
126 | - 引入 React 的内部包 jest-react、react-test-renderer,配合自制的 React-Noop-Renderer 测试并发情况下的 React case
127 |
128 | - 跑通 useEffect 调用顺序的 case
129 |
130 | - 修复了过程中发现的 Diff 算法的小 bug
131 |
132 | - Scheduler、jest-react、react-test-renderer 均采用 NPM 包形式引入
133 |
134 | ### [v9](https://github.com/BetaSu/big-react/tree/v9)
135 |
136 | 实现了 useEffect,为了实现 useEffect 回调的异步调度,引入了官方的 scheduler 模块。当前 scheduler 模块的生产环境版本放在 packages 目录下,方便对他进行修改。如果后期证实没有需要特别修改的地方,会考虑以 NPM 包的形式引入 scheduler。包括如下功能:
137 |
138 | - useEffect 实现
139 |
140 | ### [v8](https://github.com/BetaSu/big-react/tree/v8)
141 |
142 | 实现了基础功能的 Lane 模型,可以调度同步更新,并基于此实现了 batchedUpdates(批处理),包括如下功能:
143 |
144 | - Lane 模型
145 |
146 | - 带优先级的 Update 机制
147 |
148 | - Legacy 调度流程(包含 batchedUpdates)
149 |
150 | - 修复了多个子节点中 number 类型节点不支持的 bug
151 |
152 | ### [v7](https://github.com/BetaSu/big-react/tree/v7)
153 |
154 | 实现了多节点 reconcile 流程(俗称的 Diff 算法),包括如下功能:
155 |
156 | - 修复了 update 时 onClick 回调不更新的 bug
157 |
158 | - 插入多节点的 mount 流程
159 |
160 | - 插入多节点的 reconcile 流程
161 |
162 | - 浏览器环境 DOM 的移动
163 |
164 | Diff 算法的测试用例还依赖 useEffect、useRef 的实现,放在后面再实现
165 |
166 | ### [v6](https://github.com/BetaSu/big-react/tree/v6)
167 |
168 | 实现事件系统,包括如下功能:
169 |
170 | - 事件模型
171 | - onClick 事件支持(以及 onClickCapture 事件)
172 |
173 | ### [v5](https://github.com/BetaSu/big-react/tree/v5)
174 |
175 | 实现单节点 update,包括如下功能:
176 |
177 | - 浏览器环境 DOM 的删除(比如 h3 变为 p,那么就要经历删除 h3、插入 p)
178 | - 插入单节点的 reconcile 流程(包括 HostComponent、HostText)
179 | - 删除节点的 reconcile 流程(为后续 ref、useEffect 特性做准备,实现的比较完备)
180 | - Hooks 架构 update 时实现
181 |
182 | ### [v4](https://github.com/BetaSu/big-react/tree/v4)
183 |
184 | 初始化测试相关架构,包括如下功能:
185 |
186 | - 实现 React.isValidElement
187 | - jest 环境搭建
188 | - babel 配置
189 | - ReactTestUtils
190 | - 跑通关于 jsx 的 17 个官方用例
191 |
192 | ### [v3](https://github.com/BetaSu/big-react/tree/v3)
193 |
194 | 实现 useState 的 mount 时流程,包括如下功能:
195 |
196 | - FunctionComponent 类型支持
197 | - Hooks 架构 mount 时实现
198 | - useState 实现
199 |
200 | ### [v2](https://github.com/BetaSu/big-react/tree/v2)
201 |
202 | 插入单节点的 mount 流程(可以在浏览器环境渲染 DOM),包括如下功能:
203 |
204 | - 浏览器环境 DOM 的插入
205 | - HostText 类型支持
206 |
207 | ### [v1](https://github.com/BetaSu/big-react/tree/v1)
208 |
209 | 插入单节点的 render 阶段 mount 流程,包括如下功能:
210 |
211 | - JSX 转换
212 | - Fiber 架构
213 | - 插入单节点的 reconcile 流程
214 | - HostComponent 类型支持
215 | - HostRoot 类型支持
216 |
217 | 注:还未实现浏览器环境下的渲染
218 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env'],
3 | plugins: [['@babel/plugin-transform-react-jsx', { throwIfNamespace: false }]]
4 | };
5 |
--------------------------------------------------------------------------------
/demos/context/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | context测试
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/demos/context/main.tsx:
--------------------------------------------------------------------------------
1 | import { useState, createContext, useContext } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | const ctxA = createContext('deafult A');
5 | const ctxB = createContext('default B');
6 |
7 | function App() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | function Cpn() {
21 | const a = useContext(ctxA);
22 | const b = useContext(ctxB);
23 | return (
24 |
25 | A: {a} B: {b}
26 |
27 | );
28 | }
29 |
30 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/demos/context/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/demos/performance/Context.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useContext, createContext, memo } from 'react';
2 |
3 | const ctx = createContext(0);
4 |
5 | export default function App() {
6 | const [num, update] = useState(0);
7 | console.log('App render ', num);
8 | return (
9 |
10 | {
12 | update(1);
13 | }}
14 | >
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | const Cpn = memo(function () {
22 | console.log('Cpn render');
23 | return (
24 |
25 |
26 |
27 | );
28 | });
29 |
30 | function Child() {
31 | console.log('Child render');
32 | const val = useContext(ctx);
33 |
34 | return ctx: {val}
;
35 | }
36 |
--------------------------------------------------------------------------------
/demos/performance/Hook.tsx:
--------------------------------------------------------------------------------
1 | import { useState, memo, useCallback } from 'react';
2 |
3 | export default function App() {
4 | const [num, update] = useState(0);
5 | console.log('App render ', num);
6 |
7 | const addOne = useCallback(() => update((num) => num + 1), []);
8 |
9 | return (
10 |
11 |
12 | {num}
13 |
14 | );
15 | }
16 |
17 | const Cpn = memo(function ({ onClick }) {
18 | console.log('Cpn render');
19 | return (
20 | onClick()}>
21 |
22 |
23 | );
24 | });
25 |
26 | function Child() {
27 | console.log('Child render');
28 | return i am child
;
29 | }
30 |
--------------------------------------------------------------------------------
/demos/performance/Principle_demo1.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useContext, createContext, memo } from 'react';
2 |
3 | export default function App() {
4 | const [num, update] = useState(0);
5 | console.log('App render ', num);
6 |
7 | return (
8 |
9 |
10 |
num is: {num}
11 |
12 |
13 | );
14 | }
15 |
16 | function ExpensiveSubtree() {
17 | console.log('ExpensiveSubtree render');
18 | return i am child
;
19 | }
20 |
--------------------------------------------------------------------------------
/demos/performance/Principle_demo2.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useContext, createContext, memo } from 'react';
2 |
3 | export default function App() {
4 | const [num, update] = useState(0);
5 | console.log('App render ', num);
6 |
7 | return (
8 | update(num + 100)}>
9 |
17 |
num is: {num}
18 |
19 |
20 | );
21 | }
22 |
23 | function ExpensiveSubtree() {
24 | console.log('ExpensiveSubtree render');
25 | return i am child
;
26 | }
27 |
--------------------------------------------------------------------------------
/demos/performance/Simple.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export default function App() {
4 | const [num, update] = useState(0);
5 | console.log('App render ', num);
6 | return (
7 | {
9 | update(1);
10 | }}
11 | >
12 |
13 |
14 | );
15 | }
16 |
17 | function Cpn() {
18 | console.log('cpn render');
19 | return cpn
;
20 | }
21 |
--------------------------------------------------------------------------------
/demos/performance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 性能优化demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/demos/performance/main.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | // import App from './Simple';
5 | import App from './Context';
6 | // import App from './Hook';
7 | // import App from './Principle_demo1';
8 | // import App from './Principle_demo2';
9 | // import App from './memo';
10 | // import App from './useMemo';
11 |
12 | ReactDOM.createRoot(document.getElementById('root')).render();
13 |
--------------------------------------------------------------------------------
/demos/performance/memo.tsx:
--------------------------------------------------------------------------------
1 | import { useState, memo } from 'react';
2 |
3 | export default function App() {
4 | const [num, update] = useState(0);
5 | console.log('App render ', num);
6 | return (
7 | update(num + 1)}>
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | const Cpn = memo(function ({ num, name }) {
15 | console.log('render ', name);
16 | return (
17 |
18 | {name}: {num}
19 |
20 |
21 | );
22 | });
23 |
24 | function Child() {
25 | console.log('Child render');
26 | return i am child
;
27 | }
28 |
--------------------------------------------------------------------------------
/demos/performance/useMemo.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useContext, createContext, useMemo } from 'react';
2 |
3 | // 方式1:App提取 bailout四要素
4 | // 方式2:ExpensiveSubtree用memo包裹
5 | export default function App() {
6 | const [num, update] = useState(0);
7 | console.log('App render ', num);
8 |
9 | const Cpn = useMemo(() => , []);
10 |
11 | return (
12 | update(num + 100)}>
13 |
num is: {num}
14 | {Cpn}
15 |
16 | );
17 | }
18 |
19 | function ExpensiveSubtree() {
20 | console.log('ExpensiveSubtree render');
21 | return i am child
;
22 | }
23 |
--------------------------------------------------------------------------------
/demos/performance/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/demos/ref/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | noop-renderer测试
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/demos/ref/main.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | function App() {
5 | const [isDel, del] = useState(false);
6 | const divRef = useRef(null);
7 |
8 | console.warn('render divRef', divRef.current);
9 |
10 | useEffect(() => {
11 | console.warn('useEffect divRef', divRef.current);
12 | }, []);
13 |
14 | return (
15 | del(true)}>
16 | {isDel ? null : }
17 |
18 | );
19 | }
20 |
21 | function Child() {
22 | return console.warn('dom is:', dom)}>Child
;
23 | }
24 |
25 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/demos/ref/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/demos/suspense-lazy/Cpn.tsx:
--------------------------------------------------------------------------------
1 | export default function Cpn() {
2 | return Cpn
;
3 | }
4 |
--------------------------------------------------------------------------------
/demos/suspense-lazy/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Suspense
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demos/suspense-lazy/main.tsx:
--------------------------------------------------------------------------------
1 | import { Suspense, lazy } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | function delay(promise) {
5 | return new Promise((resolve) => {
6 | setTimeout(() => {
7 | resolve(promise);
8 | }, 2000);
9 | });
10 | }
11 |
12 | const Cpn = lazy(() => import('./Cpn').then((res) => delay(res)));
13 |
14 | function App() {
15 | return (
16 | loading}>
17 |
18 |
19 | );
20 | }
21 |
22 | ReactDOM.createRoot(document.getElementById('root')).render();
23 |
--------------------------------------------------------------------------------
/demos/suspense-use/Cpn.tsx:
--------------------------------------------------------------------------------
1 | import { useState, use, useEffect } from 'react';
2 |
3 | const delay = (t) =>
4 | new Promise((r) => {
5 | setTimeout(r, t);
6 | });
7 |
8 | const cachePool: any[] = [];
9 |
10 | function fetchData(id, timeout) {
11 | const cache = cachePool[id];
12 | if (cache) {
13 | return cache;
14 | }
15 | return (cachePool[id] = delay(timeout).then(() => {
16 | return { data: Math.random().toFixed(2) * 100 };
17 | }));
18 | }
19 |
20 | export function Cpn({ id, timeout }) {
21 | const [num, updateNum] = useState(0);
22 | const { data } = use(fetchData(id, timeout));
23 |
24 | if (num !== 0 && num % 5 === 0) {
25 | cachePool[id] = null;
26 | }
27 |
28 | useEffect(() => {
29 | console.log('effect create');
30 | return () => console.log('effect destroy');
31 | }, []);
32 |
33 | return (
34 | updateNum(num + 1)}>
35 | - ID: {id}
36 | - 随机数: {data}
37 | - 状态: {num}
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/demos/suspense-use/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Suspense
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/demos/suspense-use/main.tsx:
--------------------------------------------------------------------------------
1 | import { Fragment, Suspense, useState } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { Cpn } from './Cpn';
4 |
5 | // 简单例子 + 没有Suspense catch的情况
6 | function App() {
7 | return (
8 | loading...}>
9 |
10 |
11 | //
12 | );
13 | }
14 |
15 | // 嵌套Suspense
16 | // function App() {
17 | // return (
18 | // 外层...}>
19 | //
20 | // 内层...}>
21 | //
22 | //
23 | //
24 | // );
25 | // }
26 |
27 | // 缓存快速失效
28 | // function App() {
29 | // const [num, setNum] = useState(0);
30 | // return (
31 | //
32 | //
33 | // loading...
}>
34 | //
35 | //
36 | //
37 | // );
38 | // }
39 |
40 | ReactDOM.createRoot(document.getElementById('root')).render();
41 |
--------------------------------------------------------------------------------
/demos/suspense-use/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/demos/test-fc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Vite + React + TS
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demos/test-fc/main.ts:
--------------------------------------------------------------------------------
1 | import {
2 | unstable_ImmediatePriority as ImmediatePriority,
3 | unstable_UserBlockingPriority as UserBlockingPriority,
4 | unstable_NormalPriority as NormalPriority,
5 | unstable_LowPriority as LowPriority,
6 | unstable_IdlePriority as IdlePriority,
7 | unstable_scheduleCallback as scheduleCallback,
8 | unstable_shouldYield as shouldYield,
9 | CallbackNode,
10 | unstable_getFirstCallbackNode as getFirstCallbackNode,
11 | unstable_cancelCallback as cancelCallback
12 | } from 'scheduler';
13 |
14 | import './style.css';
15 | const button = document.querySelector('button');
16 | const root = document.querySelector('#root');
17 |
18 | type Priority =
19 | | typeof IdlePriority
20 | | typeof LowPriority
21 | | typeof NormalPriority
22 | | typeof UserBlockingPriority
23 | | typeof ImmediatePriority;
24 |
25 | interface Work {
26 | count: number;
27 | priority: Priority;
28 | }
29 |
30 | const workList: Work[] = [];
31 | let prevPriority: Priority = IdlePriority;
32 | let curCallback: CallbackNode | null = null;
33 |
34 | [LowPriority, NormalPriority, UserBlockingPriority, ImmediatePriority].forEach(
35 | (priority) => {
36 | const btn = document.createElement('button');
37 | root?.appendChild(btn);
38 | btn.innerText = [
39 | '',
40 | 'ImmediatePriority',
41 | 'UserBlockingPriority',
42 | 'NormalPriority',
43 | 'LowPriority'
44 | ][priority];
45 | btn.onclick = () => {
46 | workList.unshift({
47 | count: 100,
48 | priority: priority as Priority
49 | });
50 | schedule();
51 | };
52 | }
53 | );
54 |
55 | function schedule() {
56 | const cbNode = getFirstCallbackNode();
57 | const curWork = workList.sort((w1, w2) => w1.priority - w2.priority)[0];
58 |
59 | // 策略逻辑
60 | if (!curWork) {
61 | curCallback = null;
62 | cbNode && cancelCallback(cbNode);
63 | return;
64 | }
65 |
66 | const { priority: curPriority } = curWork;
67 | if (curPriority === prevPriority) {
68 | return;
69 | }
70 | // 更高优先级的work
71 | cbNode && cancelCallback(cbNode);
72 |
73 | curCallback = scheduleCallback(curPriority, perform.bind(null, curWork));
74 | }
75 |
76 | function perform(work: Work, didTimeout?: boolean) {
77 | /**
78 | * 1. work.priority
79 | * 2. 饥饿问题
80 | * 3. 时间切片
81 | */
82 | const needSync = work.priority === ImmediatePriority || didTimeout;
83 | while ((needSync || !shouldYield()) && work.count) {
84 | work.count--;
85 | insertSpan(work.priority + '');
86 | }
87 |
88 | // 中断执行 || 执行完
89 | prevPriority = work.priority;
90 |
91 | if (!work.count) {
92 | const workIndex = workList.indexOf(work);
93 | workList.splice(workIndex, 1);
94 | prevPriority = IdlePriority;
95 | }
96 |
97 | const prevCallback = curCallback;
98 | schedule();
99 | const newCallback = curCallback;
100 |
101 | if (newCallback && prevCallback === newCallback) {
102 | return perform.bind(null, work);
103 | }
104 | }
105 |
106 | function insertSpan(content) {
107 | const span = document.createElement('span');
108 | span.innerText = content;
109 | span.className = `pri-${content}`;
110 | doSomeBuzyWork(10000000);
111 | root?.appendChild(span);
112 | }
113 |
114 | function doSomeBuzyWork(len: number) {
115 | let result = 0;
116 | while (len--) {
117 | result += len;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/demos/test-fc/main.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | function App() {
5 | const [num, update] = useState(100);
6 | return (
7 | update(50)}>
8 | {new Array(num).fill(0).map((_, i) => {
9 | return {i};
10 | })}
11 |
12 | );
13 | }
14 |
15 | function Child({ children }) {
16 | const now = performance.now();
17 | while (performance.now() - now < 4) {}
18 | return {children};
19 | }
20 |
21 | const root = ReactDOM.createRoot(document.querySelector('#root'));
22 |
23 | root.render();
24 |
--------------------------------------------------------------------------------
/demos/test-fc/style.css:
--------------------------------------------------------------------------------
1 | span {
2 | word-break: break-all;
3 | }
4 |
5 | .pri-1 {
6 | color: green;
7 | }
8 | .pri-2 {
9 | color: purple;
10 | }
11 | .pri-3 {
12 | color: saddlebrown;
13 | }
14 | .pri-3 {
15 | color: black;
16 | }
17 |
--------------------------------------------------------------------------------
/demos/transition/AboutTab.tsx:
--------------------------------------------------------------------------------
1 | export default function AboutTab() {
2 | return 我是卡颂,这是我的个人页
;
3 | }
4 |
--------------------------------------------------------------------------------
/demos/transition/ContactTab.tsx:
--------------------------------------------------------------------------------
1 | export default function ContactTab() {
2 | return (
3 | <>
4 | 你可以通过如下方式联系我:
5 |
6 | - B站:魔术师卡颂
7 | - 微信:kasong555
8 |
9 | >
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/demos/transition/PostsTab.tsx:
--------------------------------------------------------------------------------
1 | const PostsTab = function PostsTab() {
2 | const items = [];
3 | for (let i = 0; i < 500; i++) {
4 | items.push();
5 | }
6 | return ;
7 | };
8 |
9 | function SlowPost({ index }) {
10 | const startTime = performance.now();
11 | while (performance.now() - startTime < 4) {}
12 |
13 | return 博文 #{index + 1};
14 | }
15 |
16 | export default PostsTab;
17 |
--------------------------------------------------------------------------------
/demos/transition/TabButton.tsx:
--------------------------------------------------------------------------------
1 | import { useTransition } from 'react';
2 |
3 | export default function TabButton({ children, isActive, onClick }) {
4 | if (isActive) {
5 | return {children};
6 | }
7 | return (
8 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/demos/transition/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Vite + React + TS
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demos/transition/main.tsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 |
3 | import { useState, useTransition } from 'react';
4 | import TabButton from './TabButton';
5 | import AboutTab from './AboutTab';
6 | import PostsTab from './PostsTab';
7 | import ContactTab from './ContactTab';
8 | import './style.css';
9 |
10 | function App() {
11 | const [isPending, startTransition] = useTransition();
12 | const [tab, setTab] = useState('about');
13 | console.log('hello');
14 | function selectTab(nextTab) {
15 | startTransition(() => {
16 | setTab(nextTab);
17 | });
18 | }
19 |
20 | return (
21 | <>
22 | selectTab('about')}>
23 | 首页
24 |
25 | selectTab('posts')}>
26 | 博客 (render慢)
27 |
28 | selectTab('contact')}
31 | >
32 | 联系我
33 |
34 |
35 | {tab === 'about' && }
36 | {tab === 'posts' && }
37 | {tab === 'contact' && }
38 | >
39 | );
40 | }
41 |
42 | const root = ReactDOM.createRoot(document.querySelector('#root'));
43 |
44 | root.render();
45 |
--------------------------------------------------------------------------------
/demos/transition/style.css:
--------------------------------------------------------------------------------
1 | button {
2 | margin: 0 5px;
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "big-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
8 | "demo": "vite serve demos/performance --config scripts/vite/vite.config.js --force",
9 | "lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages",
10 | "test": "jest --config scripts/jest/jest.config.js"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "@babel/core": "^7.20.5",
17 | "@babel/plugin-transform-react-jsx": "^7.19.0",
18 | "@babel/preset-env": "^7.20.2",
19 | "@commitlint/cli": "^17.1.2",
20 | "@commitlint/config-conventional": "^17.1.0",
21 | "@rollup/plugin-alias": "^4.0.2",
22 | "@rollup/plugin-commonjs": "^23.0.0",
23 | "@types/react": "^18.0.24",
24 | "@types/react-dom": "^18.0.8",
25 | "@types/scheduler": "^0.16.2",
26 | "@typescript-eslint/eslint-plugin": "^5.40.0",
27 | "@typescript-eslint/parser": "^5.40.0",
28 | "@vitejs/plugin-react": "^2.2.0",
29 | "commitlint": "^17.1.2",
30 | "eslint": "^8.25.0",
31 | "eslint-config-prettier": "^8.5.0",
32 | "eslint-plugin,": "link:@typescript-eslint/eslint-plugin,",
33 | "eslint-plugin-prettier": "^4.2.1",
34 | "husky": "^8.0.1",
35 | "jest": "^29.3.1",
36 | "jest-config": "^29.3.1",
37 | "jest-environment-jsdom": "^29.3.1",
38 | "jest-react": "^0.14.0",
39 | "prettier": "^2.7.1",
40 | "rimraf": "^3.0.2",
41 | "rollup": "^3.1.0",
42 | "rollup-plugin-generate-package-json": "^3.2.0",
43 | "rollup-plugin-typescript2": "^0.34.1",
44 | "typescript": "^4.8.4",
45 | "vite": "^3.2.3"
46 | },
47 | "dependencies": {
48 | "@rollup/plugin-replace": "^5.0.1",
49 | "scheduler": "^0.23.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/react-dom/client.ts:
--------------------------------------------------------------------------------
1 | import * as ReactDOM from './src/root';
2 |
3 | export default ReactDOM;
4 |
--------------------------------------------------------------------------------
/packages/react-dom/index.ts:
--------------------------------------------------------------------------------
1 | import * as ReactDOM from './src/root';
2 |
3 | export default ReactDOM;
4 |
--------------------------------------------------------------------------------
/packages/react-dom/node_modules/react-reconciler:
--------------------------------------------------------------------------------
1 | ../../react-reconciler
--------------------------------------------------------------------------------
/packages/react-dom/node_modules/shared:
--------------------------------------------------------------------------------
1 | ../../shared
--------------------------------------------------------------------------------
/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_runWithPriority,
6 | unstable_UserBlockingPriority
7 | } from 'scheduler';
8 | import { Props } from 'shared/ReactTypes';
9 |
10 | export const elementPropsKey = '__props';
11 | const validEventTypeList = ['click'];
12 |
13 | type EventCallback = (e: Event) => void;
14 |
15 | interface SyntheticEvent extends Event {
16 | __stopPropagation: boolean;
17 | }
18 |
19 | interface Paths {
20 | capture: EventCallback[];
21 | bubble: EventCallback[];
22 | }
23 |
24 | export interface DOMElement extends Element {
25 | [elementPropsKey]: Props;
26 | }
27 |
28 | // dom[xxx] = reactElemnt props
29 | export function updateFiberProps(node: DOMElement, props: Props) {
30 | node[elementPropsKey] = props;
31 | }
32 |
33 | export function initEvent(container: Container, eventType: string) {
34 | if (!validEventTypeList.includes(eventType)) {
35 | console.warn('当前不支持', eventType, '事件');
36 | return;
37 | }
38 | if (__DEV__) {
39 | console.log('初始化事件:', eventType);
40 | }
41 | container.addEventListener(eventType, (e) => {
42 | dispatchEvent(container, eventType, e);
43 | });
44 | }
45 |
46 | function createSyntheticEvent(e: Event) {
47 | const syntheticEvent = e as SyntheticEvent;
48 | syntheticEvent.__stopPropagation = false;
49 | const originStopPropagation = e.stopPropagation;
50 |
51 | syntheticEvent.stopPropagation = () => {
52 | syntheticEvent.__stopPropagation = true;
53 | if (originStopPropagation) {
54 | originStopPropagation();
55 | }
56 | };
57 | return syntheticEvent;
58 | }
59 |
60 | function dispatchEvent(container: Container, eventType: string, e: Event) {
61 | const targetElement = e.target;
62 |
63 | if (targetElement === null) {
64 | console.warn('事件不存在target', e);
65 | return;
66 | }
67 |
68 | // 1. 收集沿途的事件
69 | const { bubble, capture } = collectPaths(
70 | targetElement as DOMElement,
71 | container,
72 | eventType
73 | );
74 | // 2. 构造合成事件
75 | const se = createSyntheticEvent(e);
76 |
77 | // 3. 遍历captue
78 | triggerEventFlow(capture, se);
79 |
80 | if (!se.__stopPropagation) {
81 | // 4. 遍历bubble
82 | triggerEventFlow(bubble, se);
83 | }
84 | }
85 |
86 | function triggerEventFlow(paths: EventCallback[], se: SyntheticEvent) {
87 | for (let i = 0; i < paths.length; i++) {
88 | const callback = paths[i];
89 | unstable_runWithPriority(eventTypeToSchdulerPriority(se.type), () => {
90 | callback.call(null, se);
91 | });
92 |
93 | if (se.__stopPropagation) {
94 | break;
95 | }
96 | }
97 | }
98 |
99 | function getEventCallbackNameFromEventType(
100 | eventType: string
101 | ): string[] | undefined {
102 | return {
103 | click: ['onClickCapture', 'onClick']
104 | }[eventType];
105 | }
106 |
107 | function collectPaths(
108 | targetElement: DOMElement,
109 | container: Container,
110 | eventType: string
111 | ) {
112 | const paths: Paths = {
113 | capture: [],
114 | bubble: []
115 | };
116 |
117 | while (targetElement && targetElement !== container) {
118 | // 收集
119 | const elementProps = targetElement[elementPropsKey];
120 | if (elementProps) {
121 | // click -> onClick onClickCapture
122 | const callbackNameList = getEventCallbackNameFromEventType(eventType);
123 | if (callbackNameList) {
124 | callbackNameList.forEach((callbackName, i) => {
125 | const eventCallback = elementProps[callbackName];
126 | if (eventCallback) {
127 | if (i === 0) {
128 | // capture
129 | paths.capture.unshift(eventCallback);
130 | } else {
131 | paths.bubble.push(eventCallback);
132 | }
133 | }
134 | });
135 | }
136 | }
137 | targetElement = targetElement.parentNode as DOMElement;
138 | }
139 | return paths;
140 | }
141 |
142 | function eventTypeToSchdulerPriority(eventType: string) {
143 | switch (eventType) {
144 | case 'click':
145 | case 'keydown':
146 | case 'keyup':
147 | return unstable_ImmediatePriority;
148 | case 'scroll':
149 | return unstable_UserBlockingPriority;
150 | default:
151 | return unstable_NormalPriority;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/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 { Props } from 'shared/ReactTypes';
4 | import { updateFiberProps, DOMElement } from './SyntheticEvent';
5 |
6 | export type Container = Element;
7 | export type Instance = Element;
8 | export type TextInstance = Text;
9 |
10 | // export const createInstance = (type: string, props: any): Instance => {
11 | export const createInstance = (type: string, props: Props): Instance => {
12 | // TODO 处理props
13 | const element = document.createElement(type) as unknown;
14 | updateFiberProps(element as DOMElement, props);
15 | return element as DOMElement;
16 | };
17 |
18 | export const appendInitialChild = (
19 | parent: Instance | Container,
20 | child: Instance
21 | ) => {
22 | parent.appendChild(child);
23 | };
24 |
25 | export const createTextInstance = (content: string) => {
26 | return document.createTextNode(content);
27 | };
28 |
29 | export const appendChildToContainer = appendInitialChild;
30 |
31 | export function commitUpdate(fiber: FiberNode) {
32 | switch (fiber.tag) {
33 | case HostText:
34 | const text = fiber.memoizedProps?.content;
35 | return commitTextUpdate(fiber.stateNode, text);
36 | case HostComponent:
37 | return updateFiberProps(fiber.stateNode, fiber.memoizedProps);
38 | default:
39 | if (__DEV__) {
40 | console.warn('未实现的Update类型', fiber);
41 | }
42 | break;
43 | }
44 | }
45 |
46 | export function commitTextUpdate(textInstance: TextInstance, content: string) {
47 | textInstance.textContent = content;
48 | }
49 |
50 | export function removeChild(
51 | child: Instance | TextInstance,
52 | container: Container
53 | ) {
54 | container.removeChild(child);
55 | }
56 |
57 | export function insertChildToContainer(
58 | child: Instance,
59 | container: Container,
60 | before: Instance
61 | ) {
62 | container.insertBefore(child, before);
63 | }
64 |
65 | export const scheduleMicroTask =
66 | typeof queueMicrotask === 'function'
67 | ? queueMicrotask
68 | : typeof Promise === 'function'
69 | ? (callback: (...args: any) => void) => Promise.resolve(null).then(callback)
70 | : setTimeout;
71 |
72 | export function hideInstance(instance: Instance) {
73 | const style = (instance as HTMLElement).style;
74 | style.setProperty('display', 'none', 'important');
75 | }
76 |
77 | export function unhideInstance(instance: Instance) {
78 | const style = (instance as HTMLElement).style;
79 | style.display = '';
80 | }
81 |
82 | export function hideTextInstance(textInstance: TextInstance) {
83 | textInstance.nodeValue = '';
84 | }
85 |
86 | export function unhideTextInstance(textInstance: TextInstance, text: string) {
87 | textInstance.nodeValue = text;
88 | }
89 |
--------------------------------------------------------------------------------
/packages/react-dom/src/root.ts:
--------------------------------------------------------------------------------
1 | // ReactDOM.createRoot(root).render()
2 |
3 | import {
4 | createContainer,
5 | updateContainer
6 | } from 'react-reconciler/src/fiberReconciler';
7 | import { ReactElementType } from 'shared/ReactTypes';
8 | import { Container } from './hostConfig';
9 | import { initEvent } from './SyntheticEvent';
10 |
11 | export function createRoot(container: Container) {
12 | const root = createContainer(container);
13 |
14 | return {
15 | render(element: ReactElementType) {
16 | initEvent(container, 'click');
17 | return updateContainer(element, root);
18 | }
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/packages/react-dom/test-utils.ts:
--------------------------------------------------------------------------------
1 | import { ReactElementType } from 'shared/ReactTypes';
2 | // @ts-ignore
3 | import { createRoot } from 'react-dom';
4 |
5 | export function renderIntoDocument(element: ReactElementType) {
6 | const div = document.createElement('div');
7 | // element
8 | return createRoot(div).render(element);
9 | }
10 |
--------------------------------------------------------------------------------
/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 | }
17 |
--------------------------------------------------------------------------------
/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 | export interface TextInstance {
18 | text: string;
19 | id: number;
20 | parent: number;
21 | }
22 |
23 | let instanceCounter = 0;
24 |
25 | // export const createInstance = (type: string, props: any): Instance => {
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 | // id
42 | const prevParentID = child.parent;
43 | const parentID = 'rootID' in parent ? parent.rootID : parent.id;
44 |
45 | if (prevParentID !== -1 && prevParentID !== parentID) {
46 | throw new Error('不能重复挂载child');
47 | }
48 | child.parent = parentID;
49 | parent.children.push(child);
50 | };
51 |
52 | export const createTextInstance = (content: string) => {
53 | const instance = {
54 | text: content,
55 | id: instanceCounter++,
56 | parent: -1
57 | };
58 | return instance;
59 | };
60 |
61 | export const appendChildToContainer = (parent: Container, child: Instance) => {
62 | // id
63 | const prevParentID = child.parent;
64 |
65 | if (prevParentID !== -1 && prevParentID !== parent.rootID) {
66 | throw new Error('不能重复挂载child');
67 | }
68 | child.parent = parent.rootID;
69 | parent.children.push(child);
70 | };
71 |
72 | export function commitUpdate(fiber: FiberNode) {
73 | switch (fiber.tag) {
74 | case HostText:
75 | const text = fiber.memoizedProps?.content;
76 | return commitTextUpdate(fiber.stateNode, text);
77 | default:
78 | if (__DEV__) {
79 | console.warn('未实现的Update类型', fiber);
80 | }
81 | break;
82 | }
83 | }
84 |
85 | export function commitTextUpdate(textInstance: TextInstance, content: string) {
86 | textInstance.text = content;
87 | }
88 |
89 | export function removeChild(
90 | child: Instance | TextInstance,
91 | container: Container
92 | ) {
93 | const index = container.children.indexOf(child);
94 |
95 | if (index === -1) {
96 | throw new Error('child不存在');
97 | }
98 | container.children.splice(index, 1);
99 | }
100 |
101 | export function insertChildToContainer(
102 | child: Instance,
103 | container: Container,
104 | before: Instance
105 | ) {
106 | const beforeIndex = container.children.indexOf(before);
107 | if (beforeIndex === -1) {
108 | throw new Error('before不存在');
109 | }
110 | const index = container.children.indexOf(child);
111 | if (index !== -1) {
112 | container.children.splice(index, 1);
113 | }
114 | container.children.splice(beforeIndex, 0, child);
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 | // ReactDOM.createRoot(root).render()
2 |
3 | import { Instance } from './hostConfig';
4 | import {
5 | createContainer,
6 | updateContainer
7 | } from 'react-reconciler/src/fiberReconciler';
8 | import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
9 | import { ReactElementType } from 'shared/ReactTypes';
10 | import { Container } from './hostConfig';
11 | import * as Scheduler from 'scheduler';
12 |
13 | let idCounter = 0;
14 |
15 | export function createRoot() {
16 | const container: Container = {
17 | rootID: idCounter++,
18 | children: []
19 | };
20 |
21 | // @ts-ignore
22 | const root = createContainer(container);
23 |
24 | function getChildren(parent: Container | Instance) {
25 | if (parent) {
26 | return parent.children;
27 | }
28 | return null;
29 | }
30 |
31 | function getChildrenAsJSX(root: Container) {
32 | const children = childToJSX(getChildren(root));
33 | if (Array.isArray(children)) {
34 | return {
35 | $$typeof: REACT_ELEMENT_TYPE,
36 | type: REACT_FRAGMENT_TYPE,
37 | key: null,
38 | ref: null,
39 | props: { children },
40 | __mark: 'KaSong'
41 | };
42 | }
43 | return children;
44 | }
45 |
46 | function childToJSX(child: any): any {
47 | if (typeof child === 'string' || typeof child === 'number') {
48 | return child;
49 | }
50 |
51 | if (Array.isArray(child)) {
52 | if (child.length === 0) {
53 | return null;
54 | }
55 | if (child.length === 1) {
56 | return childToJSX(child[0]);
57 | }
58 | const children = child.map(childToJSX);
59 |
60 | if (
61 | children.every(
62 | (child) => typeof child === 'string' || typeof child === 'number'
63 | )
64 | ) {
65 | return children.join('');
66 | }
67 | // [TextInstance, TextInstance, Instance]
68 | return children;
69 | }
70 |
71 | // Instance
72 | if (Array.isArray(child.children)) {
73 | const instance: Instance = child;
74 | const children = childToJSX(instance.children);
75 | const props = instance.props;
76 |
77 | if (children !== null) {
78 | props.children = children;
79 | }
80 |
81 | return {
82 | $$typeof: REACT_ELEMENT_TYPE,
83 | type: instance.type,
84 | key: null,
85 | ref: null,
86 | props,
87 | __mark: 'KaSong'
88 | };
89 | }
90 |
91 | // TextInstance
92 | return child.text;
93 | }
94 |
95 | return {
96 | _Scheduler: Scheduler,
97 | render(element: ReactElementType) {
98 | return updateContainer(element, root);
99 | },
100 | getChildren() {
101 | return getChildren(container);
102 | },
103 | getChildrenAsJSX() {
104 | return getChildrenAsJSX(container);
105 | }
106 | };
107 | }
108 |
--------------------------------------------------------------------------------
/packages/react-reconciler/node_modules/shared:
--------------------------------------------------------------------------------
1 | ../../shared
--------------------------------------------------------------------------------
/packages/react-reconciler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-reconciler",
3 | "version": "1.0.0",
4 | "description": "React协调器",
5 | "module": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace: *"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC"
15 | }
16 |
--------------------------------------------------------------------------------
/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 | });
62 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/beginWork.ts:
--------------------------------------------------------------------------------
1 | import { ReactElementType } from 'shared/ReactTypes';
2 | import { mountChildFibers, reconcileChildFibers } from './childFibers';
3 | import {
4 | FiberNode,
5 | createFiberFromFragment,
6 | createWorkInProgress,
7 | createFiberFromOffscreen,
8 | OffscreenProps
9 | } from './fiber';
10 | import { bailoutHook, renderWithHooks } from './fiberHooks';
11 | import { Lane, NoLanes, includeSomeLanes } from './fiberLanes';
12 | import { processUpdateQueue, UpdateQueue } from './updateQueue';
13 | import {
14 | ContextProvider,
15 | Fragment,
16 | FunctionComponent,
17 | HostComponent,
18 | HostRoot,
19 | HostText,
20 | MemoComponent,
21 | OffscreenComponent,
22 | SuspenseComponent,
23 | LazyComponent
24 | } from './workTags';
25 | import {
26 | Ref,
27 | NoFlags,
28 | DidCapture,
29 | Placement,
30 | ChildDeletion
31 | } from './fiberFlags';
32 | import {
33 | prepareToReadContext,
34 | propagateContextChange,
35 | pushProvider
36 | } from './fiberContext';
37 | import { pushSuspenseHandler } from './suspenseContext';
38 | import { cloneChildFibers } from './childFibers';
39 | import { shallowEqual } from 'shared/shallowEquals';
40 |
41 | // 是否能命中bailout
42 | let didReceiveUpdate = false;
43 |
44 | export function markWipReceivedUpdate() {
45 | didReceiveUpdate = true;
46 | }
47 |
48 | // 递归中的递阶段
49 | export const beginWork = (wip: FiberNode, renderLane: Lane) => {
50 | // bailout策略
51 | didReceiveUpdate = false;
52 | const current = wip.alternate;
53 |
54 | if (current !== null) {
55 | const oldProps = current.memoizedProps;
56 | const newProps = wip.pendingProps;
57 | // 四要素~ props type
58 | // {num: 0, name: 'cpn2'}
59 | // {num: 0, name: 'cpn2'}
60 | if (oldProps !== newProps || current.type !== wip.type) {
61 | didReceiveUpdate = true;
62 | } else {
63 | // state context
64 | const hasScheduledStateOrContext = checkScheduledUpdateOrContext(
65 | current,
66 | renderLane
67 | );
68 | if (!hasScheduledStateOrContext) {
69 | // 四要素~ state context
70 | // 命中bailout
71 | didReceiveUpdate = false;
72 |
73 | switch (wip.tag) {
74 | case ContextProvider:
75 | const newValue = wip.memoizedProps.value;
76 | const context = wip.type._context;
77 | pushProvider(context, newValue);
78 | break;
79 | // TODO Suspense
80 | }
81 |
82 | return bailoutOnAlreadyFinishedWork(wip, renderLane);
83 | }
84 | }
85 | }
86 |
87 | wip.lanes = NoLanes;
88 |
89 | // 比较,返回子fiberNode
90 | switch (wip.tag) {
91 | case HostRoot:
92 | return updateHostRoot(wip, renderLane);
93 | case HostComponent:
94 | return updateHostComponent(wip);
95 | case HostText:
96 | return null;
97 | case FunctionComponent:
98 | return updateFunctionComponent(wip, wip.type, renderLane);
99 | case Fragment:
100 | return updateFragment(wip);
101 | case ContextProvider:
102 | return updateContextProvider(wip, renderLane);
103 | case SuspenseComponent:
104 | return updateSuspenseComponent(wip);
105 | case OffscreenComponent:
106 | return updateOffscreenComponent(wip);
107 | case LazyComponent:
108 | return mountLazyComponent(wip, renderLane);
109 | case MemoComponent:
110 | return updateMemoComponent(wip, renderLane);
111 | default:
112 | if (__DEV__) {
113 | console.warn('beginWork未实现的类型');
114 | }
115 | break;
116 | }
117 | return null;
118 | };
119 |
120 | function mountLazyComponent(wip: FiberNode, renderLane: Lane) {
121 | const LazyType = wip.type;
122 | const payload = LazyType._payload;
123 | const init = LazyType._init;
124 | const Component = init(payload);
125 | wip.type = Component;
126 | wip.tag = FunctionComponent;
127 | const child = updateFunctionComponent(wip, Component, renderLane);
128 | return child;
129 | }
130 |
131 | function updateMemoComponent(wip: FiberNode, renderLane: Lane) {
132 | // bailout四要素
133 | // props浅比较
134 | const current = wip.alternate;
135 | const nextProps = wip.pendingProps;
136 | const Component = wip.type.type;
137 |
138 | if (current !== null) {
139 | const prevProps = current.memoizedProps;
140 |
141 | // state context
142 | if (!checkScheduledUpdateOrContext(current, renderLane)) {
143 | // 浅比较props
144 | if (shallowEqual(prevProps, nextProps) && current.ref === wip.ref) {
145 | didReceiveUpdate = false;
146 | wip.pendingProps = prevProps;
147 |
148 | // 满足四要素
149 | wip.lanes = current.lanes;
150 | return bailoutOnAlreadyFinishedWork(wip, renderLane);
151 | }
152 | }
153 | }
154 | return updateFunctionComponent(wip, Component, renderLane);
155 | }
156 |
157 | function bailoutOnAlreadyFinishedWork(wip: FiberNode, renderLane: Lane) {
158 | if (!includeSomeLanes(wip.childLanes, renderLane)) {
159 | if (__DEV__) {
160 | console.warn('bailout整棵子树', wip);
161 | }
162 | return null;
163 | }
164 |
165 | if (__DEV__) {
166 | console.warn('bailout一个fiber', wip);
167 | }
168 | cloneChildFibers(wip);
169 | return wip.child;
170 | }
171 |
172 | function checkScheduledUpdateOrContext(
173 | current: FiberNode,
174 | renderLane: Lane
175 | ): boolean {
176 | const updateLanes = current.lanes;
177 |
178 | if (includeSomeLanes(updateLanes, renderLane)) {
179 | return true;
180 | }
181 | return false;
182 | }
183 |
184 | function updateContextProvider(wip: FiberNode, renderLane: Lane) {
185 | const providerType = wip.type;
186 | const context = providerType._context;
187 | const newProps = wip.pendingProps;
188 | const oldProps = wip.memoizedProps;
189 | const newValue = newProps.value;
190 |
191 | pushProvider(context, newValue);
192 |
193 | if (oldProps !== null) {
194 | const oldValue = oldProps.value;
195 |
196 | if (
197 | Object.is(oldValue, newValue) &&
198 | oldProps.children === newProps.children
199 | ) {
200 | return bailoutOnAlreadyFinishedWork(wip, renderLane);
201 | } else {
202 | propagateContextChange(wip, context, renderLane);
203 | }
204 | }
205 |
206 | const nextChildren = newProps.children;
207 | reconcileChildren(wip, nextChildren);
208 | return wip.child;
209 | }
210 |
211 | function updateFragment(wip: FiberNode) {
212 | const nextChildren = wip.pendingProps;
213 | reconcileChildren(wip, nextChildren);
214 | return wip.child;
215 | }
216 |
217 | function updateFunctionComponent(
218 | wip: FiberNode,
219 | Component: FiberNode['type'],
220 | renderLane: Lane
221 | ) {
222 | prepareToReadContext(wip, renderLane);
223 | // render
224 | const nextChildren = renderWithHooks(wip, Component, renderLane);
225 |
226 | const current = wip.alternate;
227 | if (current !== null && !didReceiveUpdate) {
228 | bailoutHook(wip, renderLane);
229 | return bailoutOnAlreadyFinishedWork(wip, renderLane);
230 | }
231 |
232 | reconcileChildren(wip, nextChildren);
233 | return wip.child;
234 | }
235 |
236 | function updateHostRoot(wip: FiberNode, renderLane: Lane) {
237 | const baseState = wip.memoizedState;
238 | const updateQueue = wip.updateQueue as UpdateQueue;
239 | const pending = updateQueue.shared.pending;
240 | updateQueue.shared.pending = null;
241 |
242 | const prevChildren = wip.memoizedState;
243 |
244 | const { memoizedState } = processUpdateQueue(baseState, pending, renderLane);
245 | wip.memoizedState = memoizedState;
246 |
247 | const current = wip.alternate;
248 | // 考虑RootDidNotComplete的情况,需要复用memoizedState
249 | if (current !== null) {
250 | if (!current.memoizedState) {
251 | current.memoizedState = memoizedState;
252 | }
253 | }
254 |
255 | const nextChildren = wip.memoizedState;
256 | if (prevChildren === nextChildren) {
257 | return bailoutOnAlreadyFinishedWork(wip, renderLane);
258 | }
259 | reconcileChildren(wip, nextChildren);
260 | return wip.child;
261 | }
262 |
263 | function updateHostComponent(wip: FiberNode) {
264 | const nextProps = wip.pendingProps;
265 | const nextChildren = nextProps.children;
266 | markRef(wip.alternate, wip);
267 | reconcileChildren(wip, nextChildren);
268 | return wip.child;
269 | }
270 |
271 | function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
272 | const current = wip.alternate;
273 |
274 | if (current !== null) {
275 | // update
276 | wip.child = reconcileChildFibers(wip, current?.child, children);
277 | } else {
278 | // mount
279 | wip.child = mountChildFibers(wip, null, children);
280 | }
281 | }
282 |
283 | function markRef(current: FiberNode | null, workInProgress: FiberNode) {
284 | const ref = workInProgress.ref;
285 |
286 | if (
287 | (current === null && ref !== null) ||
288 | (current !== null && current.ref !== ref)
289 | ) {
290 | workInProgress.flags |= Ref;
291 | }
292 | }
293 |
294 | function updateOffscreenComponent(workInProgress: FiberNode) {
295 | const nextProps = workInProgress.pendingProps;
296 | const nextChildren = nextProps.children;
297 | reconcileChildren(workInProgress, nextChildren);
298 | return workInProgress.child;
299 | }
300 |
301 | function updateSuspenseComponent(workInProgress: FiberNode) {
302 | const current = workInProgress.alternate;
303 | const nextProps = workInProgress.pendingProps;
304 |
305 | let showFallback = false;
306 | const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
307 |
308 | if (didSuspend) {
309 | showFallback = true;
310 | workInProgress.flags &= ~DidCapture;
311 | }
312 | const nextPrimaryChildren = nextProps.children;
313 | const nextFallbackChildren = nextProps.fallback;
314 | pushSuspenseHandler(workInProgress);
315 |
316 | if (current === null) {
317 | if (showFallback) {
318 | return mountSuspenseFallbackChildren(
319 | workInProgress,
320 | nextPrimaryChildren,
321 | nextFallbackChildren
322 | );
323 | } else {
324 | return mountSuspensePrimaryChildren(workInProgress, nextPrimaryChildren);
325 | }
326 | } else {
327 | if (showFallback) {
328 | return updateSuspenseFallbackChildren(
329 | workInProgress,
330 | nextPrimaryChildren,
331 | nextFallbackChildren
332 | );
333 | } else {
334 | return updateSuspensePrimaryChildren(workInProgress, nextPrimaryChildren);
335 | }
336 | }
337 | }
338 |
339 | function mountSuspensePrimaryChildren(
340 | workInProgress: FiberNode,
341 | primaryChildren: any
342 | ) {
343 | const primaryChildProps: OffscreenProps = {
344 | mode: 'visible',
345 | children: primaryChildren
346 | };
347 | const primaryChildFragment = createFiberFromOffscreen(primaryChildProps);
348 | workInProgress.child = primaryChildFragment;
349 | primaryChildFragment.return = workInProgress;
350 | return primaryChildFragment;
351 | }
352 |
353 | function mountSuspenseFallbackChildren(
354 | workInProgress: FiberNode,
355 | primaryChildren: any,
356 | fallbackChildren: any
357 | ) {
358 | const primaryChildProps: OffscreenProps = {
359 | mode: 'hidden',
360 | children: primaryChildren
361 | };
362 | const primaryChildFragment = createFiberFromOffscreen(primaryChildProps);
363 | const fallbackChildFragment = createFiberFromFragment(fallbackChildren, null);
364 | // 父组件Suspense已经mount,所以需要fallback标记Placement
365 | fallbackChildFragment.flags |= Placement;
366 |
367 | primaryChildFragment.return = workInProgress;
368 | fallbackChildFragment.return = workInProgress;
369 | primaryChildFragment.sibling = fallbackChildFragment;
370 | workInProgress.child = primaryChildFragment;
371 |
372 | return fallbackChildFragment;
373 | }
374 |
375 | function updateSuspensePrimaryChildren(
376 | workInProgress: FiberNode,
377 | primaryChildren: any
378 | ) {
379 | const current = workInProgress.alternate as FiberNode;
380 | const currentPrimaryChildFragment = current.child as FiberNode;
381 | const currentFallbackChildFragment: FiberNode | null =
382 | currentPrimaryChildFragment.sibling;
383 |
384 | const primaryChildProps: OffscreenProps = {
385 | mode: 'visible',
386 | children: primaryChildren
387 | };
388 |
389 | const primaryChildFragment = createWorkInProgress(
390 | currentPrimaryChildFragment,
391 | primaryChildProps
392 | );
393 | primaryChildFragment.return = workInProgress;
394 | primaryChildFragment.sibling = null;
395 | workInProgress.child = primaryChildFragment;
396 |
397 | if (currentFallbackChildFragment !== null) {
398 | const deletions = workInProgress.deletions;
399 | if (deletions === null) {
400 | workInProgress.deletions = [currentFallbackChildFragment];
401 | workInProgress.flags |= ChildDeletion;
402 | } else {
403 | deletions.push(currentFallbackChildFragment);
404 | }
405 | }
406 |
407 | return primaryChildFragment;
408 | }
409 |
410 | function updateSuspenseFallbackChildren(
411 | workInProgress: FiberNode,
412 | primaryChildren: any,
413 | fallbackChildren: any
414 | ) {
415 | const current = workInProgress.alternate as FiberNode;
416 | const currentPrimaryChildFragment = current.child as FiberNode;
417 | const currentFallbackChildFragment: FiberNode | null =
418 | currentPrimaryChildFragment.sibling;
419 |
420 | const primaryChildProps: OffscreenProps = {
421 | mode: 'hidden',
422 | children: primaryChildren
423 | };
424 | const primaryChildFragment = createWorkInProgress(
425 | currentPrimaryChildFragment,
426 | primaryChildProps
427 | );
428 | let fallbackChildFragment;
429 |
430 | if (currentFallbackChildFragment !== null) {
431 | // 可以复用
432 | fallbackChildFragment = createWorkInProgress(
433 | currentFallbackChildFragment,
434 | fallbackChildren
435 | );
436 | } else {
437 | fallbackChildFragment = createFiberFromFragment(fallbackChildren, null);
438 | fallbackChildFragment.flags |= Placement;
439 | }
440 | fallbackChildFragment.return = workInProgress;
441 | primaryChildFragment.return = workInProgress;
442 | primaryChildFragment.sibling = fallbackChildFragment;
443 | workInProgress.child = primaryChildFragment;
444 |
445 | return fallbackChildFragment;
446 | }
447 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/childFibers.ts:
--------------------------------------------------------------------------------
1 | import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
2 | import { Key, Props, ReactElementType } from 'shared/ReactTypes';
3 | import {
4 | createFiberFromElement,
5 | createFiberFromFragment,
6 | createWorkInProgress,
7 | FiberNode
8 | } from './fiber';
9 | import { ChildDeletion, Placement } from './fiberFlags';
10 | import { Fragment, HostText } from './workTags';
11 |
12 | type ExistingChildren = Map;
13 |
14 | function ChildReconciler(shouldTrackEffects: boolean) {
15 | function deleteChild(returnFiber: FiberNode, childToDelete: FiberNode) {
16 | if (!shouldTrackEffects) {
17 | return;
18 | }
19 | const deletions = returnFiber.deletions;
20 | if (deletions === null) {
21 | returnFiber.deletions = [childToDelete];
22 | returnFiber.flags |= ChildDeletion;
23 | } else {
24 | deletions.push(childToDelete);
25 | }
26 | }
27 | function deleteRemainingChildren(
28 | returnFiber: FiberNode,
29 | currentFirstChild: FiberNode | null
30 | ) {
31 | if (!shouldTrackEffects) {
32 | return;
33 | }
34 | let childToDelete = currentFirstChild;
35 | while (childToDelete !== null) {
36 | deleteChild(returnFiber, childToDelete);
37 | childToDelete = childToDelete.sibling;
38 | }
39 | }
40 | function reconcileSingleElement(
41 | returnFiber: FiberNode,
42 | currentFiber: FiberNode | null,
43 | element: ReactElementType
44 | ) {
45 | const key = element.key;
46 | while (currentFiber !== null) {
47 | // update
48 | if (currentFiber.key === key) {
49 | // key相同
50 | if (element.$$typeof === REACT_ELEMENT_TYPE) {
51 | if (currentFiber.type === element.type) {
52 | let props = element.props;
53 | if (element.type === REACT_FRAGMENT_TYPE) {
54 | props = element.props.children;
55 | }
56 | // type相同
57 | const existing = useFiber(currentFiber, props);
58 | existing.return = returnFiber;
59 | // 当前节点可复用,标记剩下的节点删除
60 | deleteRemainingChildren(returnFiber, currentFiber.sibling);
61 | return existing;
62 | }
63 |
64 | // key相同,type不同 删掉所有旧的
65 | deleteRemainingChildren(returnFiber, currentFiber);
66 | break;
67 | } else {
68 | if (__DEV__) {
69 | console.warn('还未实现的react类型', element);
70 | break;
71 | }
72 | }
73 | } else {
74 | // key不同,删掉旧的
75 | deleteChild(returnFiber, currentFiber);
76 | currentFiber = currentFiber.sibling;
77 | }
78 | }
79 | // 根据element创建fiber
80 | let fiber;
81 | if (element.type === REACT_FRAGMENT_TYPE) {
82 | fiber = createFiberFromFragment(element.props.children, key);
83 | } else {
84 | fiber = createFiberFromElement(element);
85 | }
86 | fiber.return = returnFiber;
87 | return fiber;
88 | }
89 | function reconcileSingleTextNode(
90 | returnFiber: FiberNode,
91 | currentFiber: FiberNode | null,
92 | content: string | number
93 | ) {
94 | while (currentFiber !== null) {
95 | // update
96 | if (currentFiber.tag === HostText) {
97 | // 类型没变,可以复用
98 | const existing = useFiber(currentFiber, { content });
99 | existing.return = returnFiber;
100 | deleteRemainingChildren(returnFiber, currentFiber.sibling);
101 | return existing;
102 | }
103 | deleteChild(returnFiber, currentFiber);
104 | currentFiber = currentFiber.sibling;
105 | }
106 | const fiber = new FiberNode(HostText, { content }, null);
107 | fiber.return = returnFiber;
108 | return fiber;
109 | }
110 |
111 | function placeSingleChild(fiber: FiberNode) {
112 | if (shouldTrackEffects && fiber.alternate === null) {
113 | fiber.flags |= Placement;
114 | }
115 | return fiber;
116 | }
117 |
118 | function reconcileChildrenArray(
119 | returnFiber: FiberNode,
120 | currentFirstChild: FiberNode | null,
121 | newChild: any[]
122 | ) {
123 | // 最后一个可复用fiber在current中的index
124 | let lastPlacedIndex = 0;
125 | // 创建的最后一个fiber
126 | let lastNewFiber: FiberNode | null = null;
127 | // 创建的第一个fiber
128 | let firstNewFiber: FiberNode | null = null;
129 |
130 | // 1.将current保存在map中
131 | const existingChildren: ExistingChildren = new Map();
132 | let current = currentFirstChild;
133 | while (current !== null) {
134 | const keyToUse = current.key !== null ? current.key : current.index;
135 | existingChildren.set(keyToUse, current);
136 | current = current.sibling;
137 | }
138 |
139 | for (let i = 0; i < newChild.length; i++) {
140 | // 2.遍历newChild,寻找是否可复用
141 | const after = newChild[i];
142 | const newFiber = updateFromMap(returnFiber, existingChildren, i, after);
143 |
144 | if (newFiber === null) {
145 | continue;
146 | }
147 |
148 | // 3. 标记移动还是插入
149 | newFiber.index = i;
150 | newFiber.return = returnFiber;
151 |
152 | if (lastNewFiber === null) {
153 | lastNewFiber = newFiber;
154 | firstNewFiber = newFiber;
155 | } else {
156 | lastNewFiber.sibling = newFiber;
157 | lastNewFiber = lastNewFiber.sibling;
158 | }
159 |
160 | if (!shouldTrackEffects) {
161 | continue;
162 | }
163 |
164 | const current = newFiber.alternate;
165 | if (current !== null) {
166 | const oldIndex = current.index;
167 | if (oldIndex < lastPlacedIndex) {
168 | // 移动
169 | newFiber.flags |= Placement;
170 | continue;
171 | } else {
172 | // 不移动
173 | lastPlacedIndex = oldIndex;
174 | }
175 | } else {
176 | // mount
177 | newFiber.flags |= Placement;
178 | }
179 | }
180 | // 4. 将Map中剩下的标记为删除
181 | existingChildren.forEach((fiber) => {
182 | deleteChild(returnFiber, fiber);
183 | });
184 | return firstNewFiber;
185 | }
186 |
187 | function getElementKeyToUse(element: any, index?: number): Key {
188 | if (
189 | Array.isArray(element) ||
190 | typeof element === 'string' ||
191 | typeof element === 'number' ||
192 | element === undefined ||
193 | element === null
194 | ) {
195 | return index;
196 | }
197 | return element.key !== null ? element.key : index;
198 | }
199 |
200 | function updateFromMap(
201 | returnFiber: FiberNode,
202 | existingChildren: ExistingChildren,
203 | index: number,
204 | element: any
205 | ): FiberNode | null {
206 | const keyToUse = getElementKeyToUse(element, index);
207 | const before = existingChildren.get(keyToUse);
208 |
209 | // HostText
210 | if (typeof element === 'string' || typeof element === 'number') {
211 | if (before) {
212 | if (before.tag === HostText) {
213 | existingChildren.delete(keyToUse);
214 | return useFiber(before, { content: element + '' });
215 | }
216 | }
217 | return new FiberNode(HostText, { content: element + '' }, null);
218 | }
219 |
220 | // ReactElement
221 | if (typeof element === 'object' && element !== null) {
222 | switch (element.$$typeof) {
223 | case REACT_ELEMENT_TYPE:
224 | if (element.type === REACT_FRAGMENT_TYPE) {
225 | return updateFragment(
226 | returnFiber,
227 | before,
228 | element,
229 | keyToUse,
230 | existingChildren
231 | );
232 | }
233 | if (before) {
234 | if (before.type === element.type) {
235 | existingChildren.delete(keyToUse);
236 | return useFiber(before, element.props);
237 | }
238 | }
239 | return createFiberFromElement(element);
240 | }
241 | }
242 |
243 | if (Array.isArray(element)) {
244 | return updateFragment(
245 | returnFiber,
246 | before,
247 | element,
248 | keyToUse,
249 | existingChildren
250 | );
251 | }
252 | return null;
253 | }
254 |
255 | return function reconcileChildFibers(
256 | returnFiber: FiberNode,
257 | currentFiber: FiberNode | null,
258 | newChild?: any
259 | ) {
260 | // 判断Fragment
261 | const isUnkeyedTopLevelFragment =
262 | typeof newChild === 'object' &&
263 | newChild !== null &&
264 | newChild.type === REACT_FRAGMENT_TYPE &&
265 | newChild.key === null;
266 | if (isUnkeyedTopLevelFragment) {
267 | newChild = newChild.props.children;
268 | }
269 |
270 | // 判断当前fiber的类型
271 | if (typeof newChild === 'object' && newChild !== null) {
272 | // 多节点的情况 ul> li*3
273 | if (Array.isArray(newChild)) {
274 | return reconcileChildrenArray(returnFiber, currentFiber, newChild);
275 | }
276 |
277 | switch (newChild.$$typeof) {
278 | case REACT_ELEMENT_TYPE:
279 | return placeSingleChild(
280 | reconcileSingleElement(returnFiber, currentFiber, newChild)
281 | );
282 | default:
283 | if (__DEV__) {
284 | console.warn('未实现的reconcile类型', newChild);
285 | }
286 | break;
287 | }
288 | }
289 |
290 | // HostText
291 | if (typeof newChild === 'string' || typeof newChild === 'number') {
292 | return placeSingleChild(
293 | reconcileSingleTextNode(returnFiber, currentFiber, newChild)
294 | );
295 | }
296 |
297 | if (currentFiber !== null) {
298 | // 兜底删除
299 | deleteRemainingChildren(returnFiber, currentFiber);
300 | }
301 |
302 | if (__DEV__) {
303 | console.warn('未实现的reconcile类型', newChild);
304 | }
305 | return null;
306 | };
307 | }
308 |
309 | function useFiber(fiber: FiberNode, pendingProps: Props): FiberNode {
310 | const clone = createWorkInProgress(fiber, pendingProps);
311 | clone.index = 0;
312 | clone.sibling = null;
313 | return clone;
314 | }
315 |
316 | function updateFragment(
317 | returnFiber: FiberNode,
318 | current: FiberNode | undefined,
319 | elements: any[],
320 | key: Key,
321 | existingChildren: ExistingChildren
322 | ) {
323 | let fiber;
324 | if (!current || current.tag !== Fragment) {
325 | fiber = createFiberFromFragment(elements, key);
326 | } else {
327 | existingChildren.delete(key);
328 | fiber = useFiber(current, elements);
329 | }
330 | fiber.return = returnFiber;
331 | return fiber;
332 | }
333 |
334 | export const reconcileChildFibers = ChildReconciler(true);
335 | export const mountChildFibers = ChildReconciler(false);
336 |
337 | export function cloneChildFibers(wip: FiberNode) {
338 | // child sibling
339 | if (wip.child === null) {
340 | return;
341 | }
342 | let currentChild = wip.child;
343 | let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
344 | wip.child = newChild;
345 | newChild.return = wip;
346 |
347 | while (currentChild.sibling !== null) {
348 | currentChild = currentChild.sibling;
349 | newChild = newChild.sibling = createWorkInProgress(
350 | newChild,
351 | newChild.pendingProps
352 | );
353 | newChild.return = wip;
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/commitWork.ts:
--------------------------------------------------------------------------------
1 | import {
2 | appendChildToContainer,
3 | commitUpdate,
4 | Container,
5 | hideInstance,
6 | hideTextInstance,
7 | insertChildToContainer,
8 | Instance,
9 | removeChild,
10 | unhideInstance,
11 | unhideTextInstance
12 | } from 'hostConfig';
13 | import { FiberNode, FiberRootNode, PendingPassiveEffects } from './fiber';
14 | import {
15 | ChildDeletion,
16 | Flags,
17 | LayoutMask,
18 | MutationMask,
19 | NoFlags,
20 | PassiveEffect,
21 | PassiveMask,
22 | Placement,
23 | Update,
24 | Ref,
25 | Visibility
26 | } from './fiberFlags';
27 | import { Effect, FCUpdateQueue } from './fiberHooks';
28 | import { HookHasEffect } from './hookEffectTags';
29 | import {
30 | FunctionComponent,
31 | HostComponent,
32 | HostRoot,
33 | HostText,
34 | OffscreenComponent,
35 | SuspenseComponent
36 | } from './workTags';
37 |
38 | let nextEffect: FiberNode | null = null;
39 |
40 | export const commitEffects = (
41 | phrase: 'mutation' | 'layout',
42 | mask: Flags,
43 | callback: (fiber: FiberNode, root: FiberRootNode) => void
44 | ) => {
45 | return (finishedWork: FiberNode, root: FiberRootNode) => {
46 | nextEffect = finishedWork;
47 | while (nextEffect !== null) {
48 | // 向下遍历
49 | const child: FiberNode | null = nextEffect.child;
50 |
51 | if ((nextEffect.subtreeFlags & mask) !== NoFlags && child !== null) {
52 | nextEffect = child;
53 | } else {
54 | // 向上遍历 DFS
55 | up: while (nextEffect !== null) {
56 | callback(nextEffect, root);
57 | const sibling: FiberNode | null = nextEffect.sibling;
58 |
59 | if (sibling !== null) {
60 | nextEffect = sibling;
61 | break up;
62 | }
63 | nextEffect = nextEffect.return;
64 | }
65 | }
66 | }
67 | };
68 | };
69 |
70 | const commitMutationEffectsOnFiber = (
71 | finishedWork: FiberNode,
72 | root: FiberRootNode
73 | ) => {
74 | const { flags, tag } = finishedWork;
75 | const current = finishedWork.alternate;
76 |
77 | if ((flags & Placement) !== NoFlags) {
78 | commitPlacement(finishedWork);
79 | finishedWork.flags &= ~Placement;
80 | }
81 | if ((flags & Update) !== NoFlags) {
82 | commitUpdate(finishedWork);
83 | finishedWork.flags &= ~Update;
84 | }
85 | if ((flags & ChildDeletion) !== NoFlags) {
86 | const deletions = finishedWork.deletions;
87 | if (deletions !== null) {
88 | deletions.forEach((childToDelete) => {
89 | commitDeletion(childToDelete, root);
90 | });
91 | }
92 | finishedWork.flags &= ~ChildDeletion;
93 | }
94 | if ((flags & PassiveEffect) !== NoFlags) {
95 | // 收集回调
96 | commitPassiveEffect(finishedWork, root, 'update');
97 | finishedWork.flags &= ~PassiveEffect;
98 | }
99 |
100 | if ((flags & Ref) !== NoFlags && tag === HostComponent) {
101 | if (current !== null) {
102 | safelyDetachRef(current);
103 | }
104 | }
105 | if ((flags & Visibility) !== NoFlags && tag === OffscreenComponent) {
106 | const isHidden = finishedWork.pendingProps.mode === 'hidden';
107 | hideOrUnhideAllChildren(finishedWork, isHidden);
108 | finishedWork.flags &= ~Visibility;
109 | }
110 | };
111 |
112 | // 寻找根host节点,考虑到Fragment,可能存在多个
113 | function findHostSubtreeRoot(
114 | finishedWork: FiberNode,
115 | callback: (hostSubtreeRoot: FiberNode) => void
116 | ) {
117 | let hostSubtreeRoot = null;
118 | let node = finishedWork;
119 | while (true) {
120 | if (node.tag === HostComponent) {
121 | if (hostSubtreeRoot === null) {
122 | // 还未发现 root,当前就是
123 | hostSubtreeRoot = node;
124 | callback(node);
125 | }
126 | } else if (node.tag === HostText) {
127 | if (hostSubtreeRoot === null) {
128 | // 还未发现 root,text可以是顶层节点
129 | callback(node);
130 | }
131 | } else if (
132 | node.tag === OffscreenComponent &&
133 | node.pendingProps.mode === 'hidden' &&
134 | node !== finishedWork
135 | ) {
136 | // 隐藏的OffscreenComponent跳过
137 | } else if (node.child !== null) {
138 | node.child.return = node;
139 | node = node.child;
140 | continue;
141 | }
142 |
143 | if (node === finishedWork) {
144 | return;
145 | }
146 |
147 | while (node.sibling === null) {
148 | if (node.return === null || node.return === finishedWork) {
149 | return;
150 | }
151 |
152 | if (hostSubtreeRoot === node) {
153 | hostSubtreeRoot = null;
154 | }
155 |
156 | node = node.return;
157 | }
158 |
159 | // 去兄弟节点寻找,此时当前子树的host root可以移除了
160 | if (hostSubtreeRoot === node) {
161 | hostSubtreeRoot = null;
162 | }
163 |
164 | node.sibling.return = node.return;
165 | node = node.sibling;
166 | }
167 | }
168 |
169 | function hideOrUnhideAllChildren(finishedWork: FiberNode, isHidden: boolean) {
170 | findHostSubtreeRoot(finishedWork, (hostRoot) => {
171 | const instance = hostRoot.stateNode;
172 | if (hostRoot.tag === HostComponent) {
173 | isHidden ? hideInstance(instance) : unhideInstance(instance);
174 | } else if (hostRoot.tag === HostText) {
175 | isHidden
176 | ? hideTextInstance(instance)
177 | : unhideTextInstance(instance, hostRoot.memoizedProps.content);
178 | }
179 | });
180 | }
181 |
182 | function safelyDetachRef(current: FiberNode) {
183 | const ref = current.ref;
184 | if (ref !== null) {
185 | if (typeof ref === 'function') {
186 | ref(null);
187 | } else {
188 | ref.current = null;
189 | }
190 | }
191 | }
192 |
193 | const commitLayoutEffectsOnFiber = (
194 | finishedWork: FiberNode,
195 | root: FiberRootNode
196 | ) => {
197 | const { flags, tag } = finishedWork;
198 |
199 | if ((flags & Ref) !== NoFlags && tag === HostComponent) {
200 | // 绑定新的ref
201 | safelyAttachRef(finishedWork);
202 | finishedWork.flags &= ~Ref;
203 | }
204 | };
205 |
206 | function safelyAttachRef(fiber: FiberNode) {
207 | const ref = fiber.ref;
208 | if (ref !== null) {
209 | const instance = fiber.stateNode;
210 | if (typeof ref === 'function') {
211 | ref(instance);
212 | } else {
213 | ref.current = instance;
214 | }
215 | }
216 | }
217 |
218 | export const commitMutationEffects = commitEffects(
219 | 'mutation',
220 | MutationMask | PassiveMask,
221 | commitMutationEffectsOnFiber
222 | );
223 |
224 | export const commitLayoutEffects = commitEffects(
225 | 'layout',
226 | LayoutMask,
227 | commitLayoutEffectsOnFiber
228 | );
229 |
230 | /**
231 | * 难点在于目标fiber的hostSibling可能并不是他的同级sibling
232 | * 比如: 其中:function B() {return } 所以A的hostSibling实际是B的child
233 | * 实际情况层级可能更深
234 | * 同时:一个fiber被标记Placement,那他就是不稳定的(他对应的DOM在本次commit阶段会移动),也不能作为hostSibling
235 | */
236 | function gethostSibling(fiber: FiberNode) {
237 | let node: FiberNode = fiber;
238 | findSibling: while (true) {
239 | while (node.sibling === null) {
240 | // 如果当前节点没有sibling,则找他父级sibling
241 | const parent = node.return;
242 | if (
243 | parent === null ||
244 | parent.tag === HostComponent ||
245 | parent.tag === HostRoot
246 | ) {
247 | // 没找到
248 | return null;
249 | }
250 | node = parent;
251 | }
252 | node.sibling.return = node.return;
253 | // 向同级sibling寻找
254 | node = node.sibling;
255 |
256 | while (node.tag !== HostText && node.tag !== HostComponent) {
257 | // 找到一个非Host fiber,向下找,直到找到第一个Host子孙
258 | if ((node.flags & Placement) !== NoFlags) {
259 | // 这个fiber不稳定,不能用
260 | continue findSibling;
261 | }
262 | if (node.child === null) {
263 | continue findSibling;
264 | } else {
265 | node.child.return = node;
266 | node = node.child;
267 | }
268 | }
269 |
270 | // 找到最有可能的fiber
271 | if ((node.flags & Placement) === NoFlags) {
272 | // 这是稳定的fiber,就他了
273 | return node.stateNode;
274 | }
275 | }
276 | }
277 |
278 | function commitPassiveEffect(
279 | fiber: FiberNode,
280 | root: FiberRootNode,
281 | type: keyof PendingPassiveEffects
282 | ) {
283 | // update unmount
284 | if (
285 | fiber.tag !== FunctionComponent ||
286 | (type === 'update' && (fiber.flags & PassiveEffect) === NoFlags)
287 | ) {
288 | return;
289 | }
290 | const updateQueue = fiber.updateQueue as FCUpdateQueue;
291 | if (updateQueue !== null) {
292 | if (updateQueue.lastEffect === null && __DEV__) {
293 | console.error('当FC存在PassiveEffect flag时,不应该不存在effect');
294 | }
295 | root.pendingPassiveEffects[type].push(updateQueue.lastEffect as Effect);
296 | }
297 | }
298 |
299 | function commitHookEffectList(
300 | flags: Flags,
301 | lastEffect: Effect,
302 | callback: (effect: Effect) => void
303 | ) {
304 | let effect = lastEffect.next as Effect;
305 |
306 | do {
307 | if ((effect.tag & flags) === flags) {
308 | callback(effect);
309 | }
310 | effect = effect.next as Effect;
311 | } while (effect !== lastEffect.next);
312 | }
313 |
314 | export function commitHookEffectListUnmount(flags: Flags, lastEffect: Effect) {
315 | commitHookEffectList(flags, lastEffect, (effect) => {
316 | const destroy = effect.destroy;
317 | if (typeof destroy === 'function') {
318 | destroy();
319 | }
320 | effect.tag &= ~HookHasEffect;
321 | });
322 | }
323 |
324 | export function commitHookEffectListDestroy(flags: Flags, lastEffect: Effect) {
325 | commitHookEffectList(flags, lastEffect, (effect) => {
326 | const destroy = effect.destroy;
327 | if (typeof destroy === 'function') {
328 | destroy();
329 | }
330 | });
331 | }
332 |
333 | export function commitHookEffectListCreate(flags: Flags, lastEffect: Effect) {
334 | commitHookEffectList(flags, lastEffect, (effect) => {
335 | const create = effect.create;
336 | if (typeof create === 'function') {
337 | effect.destroy = create();
338 | }
339 | });
340 | }
341 |
342 | function recordHostChildrenToDelete(
343 | childrenToDelete: FiberNode[],
344 | unmountFiber: FiberNode
345 | ) {
346 | // 1. 找到第一个root host节点
347 | const lastOne = childrenToDelete[childrenToDelete.length - 1];
348 |
349 | if (!lastOne) {
350 | childrenToDelete.push(unmountFiber);
351 | } else {
352 | let node = lastOne.sibling;
353 | while (node !== null) {
354 | if (unmountFiber === node) {
355 | childrenToDelete.push(unmountFiber);
356 | }
357 | node = node.sibling;
358 | }
359 | }
360 |
361 | // 2. 每找到一个 host节点,判断下这个节点是不是 1 找到那个节点的兄弟节点
362 | }
363 |
364 | function commitDeletion(childToDelete: FiberNode, root: FiberRootNode) {
365 | const rootChildrenToDelete: FiberNode[] = [];
366 |
367 | // 递归子树
368 | commitNestedComponent(childToDelete, (unmountFiber) => {
369 | switch (unmountFiber.tag) {
370 | case HostComponent:
371 | recordHostChildrenToDelete(rootChildrenToDelete, unmountFiber);
372 | safelyDetachRef(unmountFiber);
373 | return;
374 | case HostText:
375 | recordHostChildrenToDelete(rootChildrenToDelete, unmountFiber);
376 | return;
377 | case FunctionComponent:
378 | commitPassiveEffect(unmountFiber, root, 'unmount');
379 | return;
380 | default:
381 | if (__DEV__) {
382 | console.warn('未处理的unmount类型', unmountFiber);
383 | }
384 | }
385 | });
386 |
387 | // 移除rootHostComponent的DOM
388 | if (rootChildrenToDelete.length) {
389 | const hostParent = getHostParent(childToDelete);
390 | if (hostParent !== null) {
391 | rootChildrenToDelete.forEach((node) => {
392 | removeChild(node.stateNode, hostParent);
393 | });
394 | }
395 | }
396 | childToDelete.return = null;
397 | childToDelete.child = null;
398 | }
399 |
400 | function commitNestedComponent(
401 | root: FiberNode,
402 | onCommitUnmount: (fiber: FiberNode) => void
403 | ) {
404 | let node = root;
405 | while (true) {
406 | onCommitUnmount(node);
407 |
408 | if (node.child !== null) {
409 | // 向下遍历
410 | node.child.return = node;
411 | node = node.child;
412 | continue;
413 | }
414 | if (node === root) {
415 | // 终止条件
416 | return;
417 | }
418 | while (node.sibling === null) {
419 | if (node.return === null || node.return === root) {
420 | return;
421 | }
422 | // 向上归
423 | node = node.return;
424 | }
425 | node.sibling.return = node.return;
426 | node = node.sibling;
427 | }
428 | }
429 |
430 | const commitPlacement = (finishedWork: FiberNode) => {
431 | if (__DEV__) {
432 | console.warn('执行Placement操作', finishedWork);
433 | }
434 | // parent DOM
435 | const hostParent = getHostParent(finishedWork);
436 |
437 | // host sibling
438 | const sibling = getHostSibling(finishedWork);
439 |
440 | // finishedWork ~~ DOM append parent DOM
441 | if (hostParent !== null) {
442 | insertOrAppendPlacementNodeIntoContainer(finishedWork, hostParent, sibling);
443 | }
444 | };
445 |
446 | function getHostSibling(fiber: FiberNode) {
447 | let node: FiberNode = fiber;
448 |
449 | findSibling: while (true) {
450 | while (node.sibling === null) {
451 | const parent = node.return;
452 |
453 | if (
454 | parent === null ||
455 | parent.tag === HostComponent ||
456 | parent.tag === HostRoot
457 | ) {
458 | return null;
459 | }
460 | node = parent;
461 | }
462 | node.sibling.return = node.return;
463 | node = node.sibling;
464 |
465 | while (node.tag !== HostText && node.tag !== HostComponent) {
466 | // 向下遍历
467 | if ((node.flags & Placement) !== NoFlags) {
468 | continue findSibling;
469 | }
470 | if (node.child === null) {
471 | continue findSibling;
472 | } else {
473 | node.child.return = node;
474 | node = node.child;
475 | }
476 | }
477 |
478 | if ((node.flags & Placement) === NoFlags) {
479 | return node.stateNode;
480 | }
481 | }
482 | }
483 |
484 | function getHostParent(fiber: FiberNode): Container | null {
485 | let parent = fiber.return;
486 |
487 | while (parent) {
488 | const parentTag = parent.tag;
489 | // HostComponent HostRoot
490 | if (parentTag === HostComponent) {
491 | return parent.stateNode as Container;
492 | }
493 | if (parentTag === HostRoot) {
494 | return (parent.stateNode as FiberRootNode).container;
495 | }
496 | parent = parent.return;
497 | }
498 | if (__DEV__) {
499 | console.warn('未找到host parent');
500 | }
501 | return null;
502 | }
503 |
504 | function insertOrAppendPlacementNodeIntoContainer(
505 | finishedWork: FiberNode,
506 | hostParent: Container,
507 | before?: Instance
508 | ) {
509 | // fiber host
510 | if (finishedWork.tag === HostComponent || finishedWork.tag === HostText) {
511 | if (before) {
512 | insertChildToContainer(finishedWork.stateNode, hostParent, before);
513 | } else {
514 | appendChildToContainer(hostParent, finishedWork.stateNode);
515 | }
516 |
517 | return;
518 | }
519 | const child = finishedWork.child;
520 | if (child !== null) {
521 | insertOrAppendPlacementNodeIntoContainer(child, hostParent);
522 | let sibling = child.sibling;
523 |
524 | while (sibling !== null) {
525 | insertOrAppendPlacementNodeIntoContainer(sibling, hostParent);
526 | sibling = sibling.sibling;
527 | }
528 | }
529 | }
530 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/completeWork.ts:
--------------------------------------------------------------------------------
1 | import {
2 | appendInitialChild,
3 | Container,
4 | createInstance,
5 | createTextInstance,
6 | Instance
7 | } from 'hostConfig';
8 | import { FiberNode, OffscreenProps } from './fiber';
9 | import { NoFlags, Ref, Update, Visibility } from './fiberFlags';
10 | import {
11 | HostRoot,
12 | HostText,
13 | HostComponent,
14 | FunctionComponent,
15 | Fragment,
16 | ContextProvider,
17 | OffscreenComponent,
18 | SuspenseComponent,
19 | MemoComponent
20 | } from './workTags';
21 | import { popProvider } from './fiberContext';
22 | import { popSuspenseHandler } from './suspenseContext';
23 | import { mergeLanes, NoLanes } from './fiberLanes';
24 |
25 | function markUpdate(fiber: FiberNode) {
26 | fiber.flags |= Update;
27 | }
28 |
29 | function markRef(fiber: FiberNode) {
30 | fiber.flags |= Ref;
31 | }
32 |
33 | export const completeWork = (wip: FiberNode) => {
34 | // 递归中的归
35 |
36 | const newProps = wip.pendingProps;
37 | const current = wip.alternate;
38 |
39 | switch (wip.tag) {
40 | case HostComponent:
41 | if (current !== null && wip.stateNode) {
42 | // TODO update
43 | // 1. props是否变化 {onClick: xx} {onClick: xxx}
44 | // 2. 变了 Update flag
45 | // className style
46 | markUpdate(wip);
47 | // 标记Ref
48 | if (current.ref !== wip.ref) {
49 | markRef(wip);
50 | }
51 | } else {
52 | // mount
53 | // 1. 构建DOM
54 | // const instance = createInstance(wip.type, newProps);
55 | const instance = createInstance(wip.type, newProps);
56 | // 2. 将DOM插入到DOM树中
57 | appendAllChildren(instance, wip);
58 | wip.stateNode = instance;
59 | // 标记Ref
60 | if (wip.ref !== null) {
61 | markRef(wip);
62 | }
63 | }
64 | bubbleProperties(wip);
65 | return null;
66 | case HostText:
67 | if (current !== null && wip.stateNode) {
68 | // update
69 | const oldText = current.memoizedProps?.content;
70 | const newText = newProps.content;
71 | if (oldText !== newText) {
72 | markUpdate(wip);
73 | }
74 | } else {
75 | // 1. 构建DOM
76 | const instance = createTextInstance(newProps.content);
77 | wip.stateNode = instance;
78 | }
79 | bubbleProperties(wip);
80 | return null;
81 | case HostRoot:
82 | case FunctionComponent:
83 | case Fragment:
84 | case OffscreenComponent:
85 | case MemoComponent:
86 | bubbleProperties(wip);
87 | return null;
88 | case ContextProvider:
89 | const context = wip.type._context;
90 | popProvider(context);
91 | bubbleProperties(wip);
92 | return null;
93 | case SuspenseComponent:
94 | popSuspenseHandler();
95 |
96 | const offscreenFiber = wip.child as FiberNode;
97 | const isHidden = offscreenFiber.pendingProps.mode === 'hidden';
98 | const currentOffscreenFiber = offscreenFiber.alternate;
99 | if (currentOffscreenFiber !== null) {
100 | const wasHidden = currentOffscreenFiber.pendingProps.mode === 'hidden';
101 |
102 | if (isHidden !== wasHidden) {
103 | // 可见性变化
104 | offscreenFiber.flags |= Visibility;
105 | bubbleProperties(offscreenFiber);
106 | }
107 | } else if (isHidden) {
108 | // mount时hidden
109 | offscreenFiber.flags |= Visibility;
110 | bubbleProperties(offscreenFiber);
111 | }
112 | bubbleProperties(wip);
113 | return null;
114 | default:
115 | if (__DEV__) {
116 | console.warn('未处理的completeWork情况', wip);
117 | }
118 | break;
119 | }
120 | };
121 |
122 | function appendAllChildren(parent: Container | Instance, wip: FiberNode) {
123 | let node = wip.child;
124 |
125 | while (node !== null) {
126 | if (node.tag === HostComponent || node.tag === HostText) {
127 | appendInitialChild(parent, node?.stateNode);
128 | } else if (node.child !== null) {
129 | node.child.return = node;
130 | node = node.child;
131 | continue;
132 | }
133 |
134 | if (node === wip) {
135 | return;
136 | }
137 |
138 | while (node.sibling === null) {
139 | if (node.return === null || node.return === wip) {
140 | return;
141 | }
142 | node = node?.return;
143 | }
144 | node.sibling.return = node.return;
145 | node = node.sibling;
146 | }
147 | }
148 |
149 | function bubbleProperties(wip: FiberNode) {
150 | let subtreeFlags = NoFlags;
151 | let child = wip.child;
152 | let newChildLanes = NoLanes;
153 |
154 | while (child !== null) {
155 | subtreeFlags |= child.subtreeFlags;
156 | subtreeFlags |= child.flags;
157 |
158 | // child.lanes child.childLanes
159 | newChildLanes = mergeLanes(
160 | newChildLanes,
161 | mergeLanes(child.lanes, child.childLanes)
162 | );
163 |
164 | child.return = wip;
165 | child = child.sibling;
166 | }
167 | wip.subtreeFlags |= subtreeFlags;
168 | wip.childLanes = newChildLanes;
169 | }
170 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiber.ts:
--------------------------------------------------------------------------------
1 | import { Props, Key, Ref, ReactElementType, Wakeable } from 'shared/ReactTypes';
2 | import {
3 | ContextProvider,
4 | Fragment,
5 | FunctionComponent,
6 | HostComponent,
7 | WorkTag,
8 | SuspenseComponent,
9 | OffscreenComponent,
10 | LazyComponent,
11 | MemoComponent
12 | } from './workTags';
13 | import { Flags, NoFlags } from './fiberFlags';
14 | import { Container } from 'hostConfig';
15 | import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes';
16 | import { Effect } from './fiberHooks';
17 | import { CallbackNode } from 'scheduler';
18 | import {
19 | REACT_MEMO_TYPE,
20 | REACT_PROVIDER_TYPE,
21 | REACT_LAZY_TYPE,
22 | REACT_SUSPENSE_TYPE
23 | } from 'shared/ReactSymbols';
24 | import { ContextItem } from './fiberContext';
25 |
26 | interface FiberDependencies {
27 | firstContext: ContextItem | null;
28 | lanes: Lanes;
29 | }
30 |
31 | export class FiberNode {
32 | type: any;
33 | tag: WorkTag;
34 | pendingProps: Props;
35 | key: Key;
36 | stateNode: any;
37 | ref: Ref | null;
38 |
39 | return: FiberNode | null;
40 | sibling: FiberNode | null;
41 | child: FiberNode | null;
42 | index: number;
43 |
44 | memoizedProps: Props | null;
45 | memoizedState: any;
46 | alternate: FiberNode | null;
47 | flags: Flags;
48 | subtreeFlags: Flags;
49 | updateQueue: unknown;
50 | deletions: FiberNode[] | null;
51 |
52 | lanes: Lanes;
53 | childLanes: Lanes;
54 |
55 | dependencies: FiberDependencies | null;
56 |
57 | constructor(tag: WorkTag, pendingProps: Props, key: Key) {
58 | // 实例
59 | this.tag = tag;
60 | this.key = key || null;
61 | // HostComponent div DOM
62 | this.stateNode = null;
63 | // FunctionComponent () => {}
64 | this.type = null;
65 |
66 | // 构成树状结构
67 | this.return = null;
68 | this.sibling = null;
69 | this.child = null;
70 | this.index = 0;
71 |
72 | this.ref = null;
73 |
74 | // 作为工作单元
75 | this.pendingProps = pendingProps;
76 | this.memoizedProps = null;
77 | this.memoizedState = null;
78 | this.updateQueue = null;
79 |
80 | this.alternate = null;
81 | // 副作用
82 | this.flags = NoFlags;
83 | this.subtreeFlags = NoFlags;
84 | this.deletions = null;
85 |
86 | this.lanes = NoLanes;
87 | this.childLanes = NoLanes;
88 |
89 | this.dependencies = null;
90 | }
91 | }
92 |
93 | export interface PendingPassiveEffects {
94 | unmount: Effect[];
95 | update: Effect[];
96 | }
97 |
98 | export class FiberRootNode {
99 | container: Container;
100 | current: FiberNode;
101 | finishedWork: FiberNode | null;
102 | pendingLanes: Lanes;
103 | suspendedLanes: Lanes;
104 | pingedLanes: Lanes;
105 | finishedLane: Lane;
106 | pendingPassiveEffects: PendingPassiveEffects;
107 |
108 | callbackNode: CallbackNode | null;
109 | callbackPriority: Lane;
110 |
111 | pingCache: WeakMap
, Set> | null;
112 |
113 | constructor(container: Container, hostRootFiber: FiberNode) {
114 | this.container = container;
115 | this.current = hostRootFiber;
116 | hostRootFiber.stateNode = this;
117 | this.finishedWork = null;
118 | this.pendingLanes = NoLanes;
119 | this.suspendedLanes = NoLanes;
120 | this.pingedLanes = NoLanes;
121 | this.finishedLane = NoLane;
122 |
123 | this.callbackNode = null;
124 | this.callbackPriority = NoLane;
125 |
126 | this.pendingPassiveEffects = {
127 | unmount: [],
128 | update: []
129 | };
130 |
131 | this.pingCache = null;
132 | }
133 | }
134 |
135 | export const createWorkInProgress = (
136 | current: FiberNode,
137 | pendingProps: Props
138 | ): FiberNode => {
139 | let wip = current.alternate;
140 | if (wip === null) {
141 | // mount
142 | wip = new FiberNode(current.tag, pendingProps, current.key);
143 | wip.stateNode = current.stateNode;
144 |
145 | wip.alternate = current;
146 | current.alternate = wip;
147 | } else {
148 | // update
149 | wip.pendingProps = pendingProps;
150 | wip.flags = NoFlags;
151 | wip.subtreeFlags = NoFlags;
152 | wip.deletions = null;
153 | }
154 | wip.type = current.type;
155 | wip.updateQueue = current.updateQueue;
156 | wip.child = current.child;
157 | wip.memoizedProps = current.memoizedProps;
158 | wip.memoizedState = current.memoizedState;
159 | wip.ref = current.ref;
160 |
161 | wip.lanes = current.lanes;
162 | wip.childLanes = current.childLanes;
163 |
164 | const currentDeps = current.dependencies;
165 | wip.dependencies =
166 | currentDeps === null
167 | ? null
168 | : {
169 | lanes: currentDeps.lanes,
170 | firstContext: currentDeps.firstContext
171 | };
172 |
173 | return wip;
174 | };
175 |
176 | export function createFiberFromElement(element: ReactElementType): FiberNode {
177 | const { type, key, props, ref } = element;
178 | let fiberTag: WorkTag = FunctionComponent;
179 |
180 | if (typeof type === 'string') {
181 | // type: 'div'
182 | fiberTag = HostComponent;
183 | } else if (typeof type === 'object') {
184 | switch (type.$$typeof) {
185 | case REACT_PROVIDER_TYPE:
186 | fiberTag = ContextProvider;
187 | break;
188 | case REACT_MEMO_TYPE:
189 | fiberTag = MemoComponent;
190 | break;
191 | case REACT_LAZY_TYPE:
192 | fiberTag = LazyComponent;
193 | break;
194 | default:
195 | console.warn('未定义的type类型', element);
196 | break;
197 | }
198 | } else if (type === REACT_SUSPENSE_TYPE) {
199 | fiberTag = SuspenseComponent;
200 | } else if (typeof type !== 'function' && __DEV__) {
201 | console.warn('为定义的type类型', element);
202 | }
203 | const fiber = new FiberNode(fiberTag, props, key);
204 | fiber.type = type;
205 | fiber.ref = ref;
206 | return fiber;
207 | }
208 |
209 | export function createFiberFromFragment(elements: any[], key: Key): FiberNode {
210 | const fiber = new FiberNode(Fragment, elements, key);
211 | return fiber;
212 | }
213 |
214 | export interface OffscreenProps {
215 | mode: 'visible' | 'hidden';
216 | children: any;
217 | }
218 |
219 | export function createFiberFromOffscreen(pendingProps: OffscreenProps) {
220 | const fiber = new FiberNode(OffscreenComponent, pendingProps, null);
221 | // TODO stateNode
222 | return fiber;
223 | }
224 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberContext.ts:
--------------------------------------------------------------------------------
1 | import { ReactContext } from 'shared/ReactTypes';
2 | import { FiberNode } from './fiber';
3 | import {
4 | Lane,
5 | NoLanes,
6 | includeSomeLanes,
7 | isSubsetOfLanes,
8 | mergeLanes
9 | } from './fiberLanes';
10 | import { markWipReceivedUpdate } from './beginWork';
11 | import { ContextProvider } from './workTags';
12 |
13 | let lastContextDep: ContextItem | null = null;
14 |
15 | export interface ContextItem {
16 | context: ReactContext;
17 | memoizedState: Value;
18 | next: ContextItem | null;
19 | }
20 |
21 | let prevContextValue: any = null;
22 | const prevContextValueStack: any[] = [];
23 |
24 | export function pushProvider(context: ReactContext, newValue: T) {
25 | prevContextValueStack.push(prevContextValue);
26 |
27 | prevContextValue = context._currentValue;
28 | context._currentValue = newValue;
29 | }
30 |
31 | export function popProvider(context: ReactContext) {
32 | context._currentValue = prevContextValue;
33 |
34 | prevContextValue = prevContextValueStack.pop();
35 | }
36 |
37 | export function prepareToReadContext(wip: FiberNode, renderLane: Lane) {
38 | lastContextDep = null;
39 |
40 | const deps = wip.dependencies;
41 | if (deps !== null) {
42 | const firstContext = deps.firstContext;
43 | if (firstContext !== null) {
44 | if (includeSomeLanes(deps.lanes, renderLane)) {
45 | markWipReceivedUpdate();
46 | }
47 | deps.firstContext = null;
48 | }
49 | }
50 | }
51 |
52 | export function readContext(
53 | consumer: FiberNode | null,
54 | context: ReactContext
55 | ): T {
56 | if (consumer === null) {
57 | throw new Error('只能在函数组件中调用useContext');
58 | }
59 | const value = context._currentValue;
60 |
61 | // 建立 fiber -> context
62 | const contextItem: ContextItem = {
63 | context,
64 | next: null,
65 | memoizedState: value
66 | };
67 |
68 | if (lastContextDep === null) {
69 | lastContextDep = contextItem;
70 | consumer.dependencies = {
71 | firstContext: contextItem,
72 | lanes: NoLanes
73 | };
74 | } else {
75 | lastContextDep = lastContextDep.next = contextItem;
76 | }
77 |
78 | return value;
79 | }
80 |
81 | export function propagateContextChange(
82 | wip: FiberNode,
83 | context: ReactContext,
84 | renderLane: Lane
85 | ) {
86 | let fiber = wip.child;
87 | if (fiber !== null) {
88 | fiber.return = wip;
89 | }
90 |
91 | while (fiber !== null) {
92 | let nextFiber = null;
93 | const deps = fiber.dependencies;
94 | if (deps !== null) {
95 | nextFiber = fiber.child;
96 |
97 | let contextItem = deps.firstContext;
98 | while (contextItem !== null) {
99 | if (contextItem.context === context) {
100 | // 找到了
101 | fiber.lanes = mergeLanes(fiber.lanes, renderLane);
102 | const alternate = fiber.alternate;
103 | if (alternate !== null) {
104 | alternate.lanes = mergeLanes(alternate.lanes, renderLane);
105 | }
106 | // 往上
107 | scheduleContextWorkOnParentPath(fiber.return, wip, renderLane);
108 | deps.lanes = mergeLanes(deps.lanes, renderLane);
109 | break;
110 | }
111 | contextItem = contextItem.next;
112 | }
113 | } else if (fiber.tag === ContextProvider) {
114 | nextFiber = fiber.type === wip.type ? null : fiber.child;
115 | } else {
116 | nextFiber = fiber.child;
117 | }
118 |
119 | if (nextFiber !== null) {
120 | nextFiber.return = fiber;
121 | } else {
122 | // 到了叶子结点
123 | nextFiber = fiber;
124 | while (nextFiber !== null) {
125 | if (nextFiber === wip) {
126 | nextFiber = null;
127 | break;
128 | }
129 | const sibling = nextFiber.sibling;
130 | if (sibling !== null) {
131 | sibling.return = nextFiber.return;
132 | nextFiber = sibling;
133 | break;
134 | }
135 | nextFiber = nextFiber.return;
136 | }
137 | }
138 | fiber = nextFiber;
139 | }
140 | }
141 |
142 | function scheduleContextWorkOnParentPath(
143 | from: FiberNode | null,
144 | to: FiberNode,
145 | renderLane: Lane
146 | ) {
147 | let node = from;
148 |
149 | while (node !== null) {
150 | const alternate = node.alternate;
151 |
152 | if (!isSubsetOfLanes(node.childLanes, renderLane)) {
153 | node.childLanes = mergeLanes(node.childLanes, renderLane);
154 | if (alternate !== null) {
155 | alternate.childLanes = mergeLanes(alternate.childLanes, renderLane);
156 | }
157 | } else if (
158 | alternate !== null &&
159 | !isSubsetOfLanes(alternate.childLanes, renderLane)
160 | ) {
161 | alternate.childLanes = mergeLanes(alternate.childLanes, renderLane);
162 | }
163 |
164 | if (node === to) {
165 | break;
166 | }
167 | node = node.return;
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberFlags.ts:
--------------------------------------------------------------------------------
1 | export type Flags = number;
2 |
3 | export const NoFlags = 0b0000000;
4 | export const Placement = 0b0000001;
5 | export const Update = 0b0000010;
6 | export const ChildDeletion = 0b0000100;
7 |
8 | export const PassiveEffect = 0b0001000;
9 | export const Ref = 0b0010000;
10 |
11 | export const Visibility = 0b0100000;
12 |
13 | // 捕获到 something
14 | export const DidCapture = 0b1000000;
15 |
16 | // unwind应该捕获、还未捕获到
17 | export const ShouldCapture = 0b1000000000000;
18 |
19 | export const MutationMask =
20 | Placement | Update | ChildDeletion | Ref | Visibility;
21 | export const LayoutMask = Ref;
22 |
23 | export const PassiveMask = PassiveEffect | ChildDeletion;
24 |
25 | export const HostEffectMask =
26 | MutationMask | LayoutMask | PassiveMask | DidCapture;
27 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberHooks.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from 'react/src/currentDispatcher';
2 | import { Dispatcher } from 'react/src/currentDispatcher';
3 | import internals from 'shared/internals';
4 | import { Action, ReactContext, Thenable, Usable } from 'shared/ReactTypes';
5 | import { FiberNode } from './fiber';
6 | import { Flags, PassiveEffect } from './fiberFlags';
7 | import {
8 | Lane,
9 | NoLane,
10 | NoLanes,
11 | mergeLanes,
12 | removeLanes,
13 | requestUpdateLane
14 | } from './fiberLanes';
15 | import { HookHasEffect, Passive } from './hookEffectTags';
16 | import {
17 | basicStateReducer,
18 | createUpdate,
19 | createUpdateQueue,
20 | enqueueUpdate,
21 | processUpdateQueue,
22 | Update,
23 | UpdateQueue
24 | } from './updateQueue';
25 | import { scheduleUpdateOnFiber } from './workLoop';
26 | import { trackUsedThenable } from './thenable';
27 | import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols';
28 | import { markWipReceivedUpdate } from './beginWork';
29 | import { readContext as readContextOrigin } from './fiberContext';
30 |
31 | let currentlyRenderingFiber: FiberNode | null = null;
32 | let workInProgressHook: Hook | null = null;
33 | let currentHook: Hook | null = null;
34 | let renderLane: Lane = NoLane;
35 |
36 | const { currentDispatcher, currentBatchConfig } = internals;
37 |
38 | function readContext(context: ReactContext): Value {
39 | const consumer = currentlyRenderingFiber as FiberNode;
40 | return readContextOrigin(consumer, context);
41 | }
42 | interface Hook {
43 | memoizedState: any;
44 | updateQueue: unknown;
45 | next: Hook | null;
46 | baseState: any;
47 | baseQueue: Update | null;
48 | }
49 |
50 | export interface Effect {
51 | tag: Flags;
52 | create: EffectCallback | void;
53 | destroy: EffectCallback | void;
54 | deps: HookDeps;
55 | next: Effect | null;
56 | }
57 |
58 | export interface FCUpdateQueue extends UpdateQueue {
59 | lastEffect: Effect | null;
60 | lastRenderedState: State;
61 | }
62 |
63 | type EffectCallback = () => void;
64 | export type HookDeps = any[] | null;
65 |
66 | export function renderWithHooks(
67 | wip: FiberNode,
68 | Component: FiberNode['type'],
69 | lane: Lane
70 | ) {
71 | // 赋值操作
72 | currentlyRenderingFiber = wip;
73 | // 重置 hooks链表
74 | wip.memoizedState = null;
75 | // 重置 effect链表
76 | wip.updateQueue = null;
77 | renderLane = lane;
78 |
79 | const current = wip.alternate;
80 |
81 | if (current !== null) {
82 | // update
83 | currentDispatcher.current = HooksDispatcherOnUpdate;
84 | } else {
85 | // mount
86 | currentDispatcher.current = HooksDispatcherOnMount;
87 | }
88 |
89 | const props = wip.pendingProps;
90 | // FC render
91 | const children = Component(props);
92 |
93 | // 重置操作
94 | currentlyRenderingFiber = null;
95 | workInProgressHook = null;
96 | currentHook = null;
97 | renderLane = NoLane;
98 | return children;
99 | }
100 |
101 | const HooksDispatcherOnMount: Dispatcher = {
102 | useState: mountState,
103 | useEffect: mountEffect,
104 | useTransition: mountTransition,
105 | useRef: mountRef,
106 | useContext: readContext,
107 | use,
108 | useMemo: mountMemo,
109 | useCallback: mountCallback
110 | };
111 |
112 | const HooksDispatcherOnUpdate: Dispatcher = {
113 | useState: updateState,
114 | useEffect: updateEffect,
115 | useTransition: updateTransition,
116 | useRef: updateRef,
117 | useContext: readContext,
118 | use,
119 | useMemo: updateMemo,
120 | useCallback: updateCallback
121 | };
122 |
123 | function mountEffect(create: EffectCallback | void, deps: HookDeps | void) {
124 | const hook = mountWorkInProgressHook();
125 | const nextDeps = deps === undefined ? null : deps;
126 | (currentlyRenderingFiber as FiberNode).flags |= PassiveEffect;
127 |
128 | hook.memoizedState = pushEffect(
129 | Passive | HookHasEffect,
130 | create,
131 | undefined,
132 | nextDeps
133 | );
134 | }
135 |
136 | function updateEffect(create: EffectCallback | void, deps: HookDeps | void) {
137 | const hook = updateWorkInProgressHook();
138 | const nextDeps = deps === undefined ? null : deps;
139 | let destroy: EffectCallback | void;
140 |
141 | if (currentHook !== null) {
142 | const prevEffect = currentHook.memoizedState as Effect;
143 | destroy = prevEffect.destroy;
144 |
145 | if (nextDeps !== null) {
146 | // 浅比较依赖
147 | const prevDeps = prevEffect.deps;
148 | if (areHookInputsEqual(nextDeps, prevDeps)) {
149 | hook.memoizedState = pushEffect(Passive, create, destroy, nextDeps);
150 | return;
151 | }
152 | }
153 | // 浅比较 不相等
154 | (currentlyRenderingFiber as FiberNode).flags |= PassiveEffect;
155 | hook.memoizedState = pushEffect(
156 | Passive | HookHasEffect,
157 | create,
158 | destroy,
159 | nextDeps
160 | );
161 | }
162 | }
163 |
164 | function areHookInputsEqual(nextDeps: HookDeps, prevDeps: HookDeps) {
165 | if (prevDeps === null || nextDeps === null) {
166 | return false;
167 | }
168 | for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
169 | if (Object.is(prevDeps[i], nextDeps[i])) {
170 | continue;
171 | }
172 | return false;
173 | }
174 | return true;
175 | }
176 |
177 | function pushEffect(
178 | hookFlags: Flags,
179 | create: EffectCallback | void,
180 | destroy: EffectCallback | void,
181 | deps: HookDeps
182 | ): Effect {
183 | const effect: Effect = {
184 | tag: hookFlags,
185 | create,
186 | destroy,
187 | deps,
188 | next: null
189 | };
190 | const fiber = currentlyRenderingFiber as FiberNode;
191 | const updateQueue = fiber.updateQueue as FCUpdateQueue;
192 | if (updateQueue === null) {
193 | const updateQueue = createFCUpdateQueue();
194 | fiber.updateQueue = updateQueue;
195 | effect.next = effect;
196 | updateQueue.lastEffect = effect;
197 | } else {
198 | // 插入effect
199 | const lastEffect = updateQueue.lastEffect;
200 | if (lastEffect === null) {
201 | effect.next = effect;
202 | updateQueue.lastEffect = effect;
203 | } else {
204 | const firstEffect = lastEffect.next;
205 | lastEffect.next = effect;
206 | effect.next = firstEffect;
207 | updateQueue.lastEffect = effect;
208 | }
209 | }
210 | return effect;
211 | }
212 |
213 | function createFCUpdateQueue() {
214 | const updateQueue = createUpdateQueue() as FCUpdateQueue;
215 | updateQueue.lastEffect = null;
216 | return updateQueue;
217 | }
218 |
219 | function updateState(): [State, Dispatch] {
220 | // 找到当前useState对应的hook数据
221 | const hook = updateWorkInProgressHook();
222 |
223 | // 计算新state的逻辑
224 | const queue = hook.updateQueue as FCUpdateQueue;
225 | const baseState = hook.baseState;
226 |
227 | const pending = queue.shared.pending;
228 | const current = currentHook as Hook;
229 | let baseQueue = current.baseQueue;
230 |
231 | if (pending !== null) {
232 | // pending baseQueue update保存在current中
233 | if (baseQueue !== null) {
234 | // baseQueue b2 -> b0 -> b1 -> b2
235 | // pendingQueue p2 -> p0 -> p1 -> p2
236 | // b0
237 | const baseFirst = baseQueue.next;
238 | // p0
239 | const pendingFirst = pending.next;
240 | // b2 -> p0
241 | baseQueue.next = pendingFirst;
242 | // p2 -> b0
243 | pending.next = baseFirst;
244 | // p2 -> b0 -> b1 -> b2 -> p0 -> p1 -> p2
245 | }
246 | baseQueue = pending;
247 | // 保存在current中
248 | current.baseQueue = pending;
249 | queue.shared.pending = null;
250 | }
251 |
252 | if (baseQueue !== null) {
253 | const prevState = hook.memoizedState;
254 | const {
255 | memoizedState,
256 | baseQueue: newBaseQueue,
257 | baseState: newBaseState
258 | } = processUpdateQueue(baseState, baseQueue, renderLane, (update) => {
259 | const skippedLane = update.lane;
260 | const fiber = currentlyRenderingFiber as FiberNode;
261 | // NoLanes
262 | fiber.lanes = mergeLanes(fiber.lanes, skippedLane);
263 | });
264 |
265 | // NaN === NaN // false
266 | // Object.is true
267 |
268 | // +0 === -0 // true
269 | // Object.is false
270 | if (!Object.is(prevState, memoizedState)) {
271 | markWipReceivedUpdate();
272 | }
273 |
274 | hook.memoizedState = memoizedState;
275 | hook.baseState = newBaseState;
276 | hook.baseQueue = newBaseQueue;
277 |
278 | queue.lastRenderedState = memoizedState;
279 | }
280 |
281 | return [hook.memoizedState, queue.dispatch as Dispatch];
282 | }
283 |
284 | function updateWorkInProgressHook(): Hook {
285 | // TODO render阶段触发的更新
286 | let nextCurrentHook: Hook | null;
287 |
288 | if (currentHook === null) {
289 | // 这是这个FC update时的第一个hook
290 | const current = (currentlyRenderingFiber as FiberNode).alternate;
291 | if (current !== null) {
292 | nextCurrentHook = current.memoizedState;
293 | } else {
294 | // mount
295 | nextCurrentHook = null;
296 | }
297 | } else {
298 | // 这个FC update时 后续的hook
299 | nextCurrentHook = currentHook.next;
300 | }
301 |
302 | if (nextCurrentHook === null) {
303 | // mount/update u1 u2 u3
304 | // update u1 u2 u3 u4
305 | throw new Error(
306 | `组件 ${currentlyRenderingFiber?.type.name} 本次执行时的Hook比上次执行时多`
307 | );
308 | }
309 |
310 | currentHook = nextCurrentHook as Hook;
311 | const newHook: Hook = {
312 | memoizedState: currentHook.memoizedState,
313 | updateQueue: currentHook.updateQueue,
314 | next: null,
315 | baseQueue: currentHook.baseQueue,
316 | baseState: currentHook.baseState
317 | };
318 | if (workInProgressHook === null) {
319 | // mount时 第一个hook
320 | if (currentlyRenderingFiber === null) {
321 | throw new Error('请在函数组件内调用hook');
322 | } else {
323 | workInProgressHook = newHook;
324 | currentlyRenderingFiber.memoizedState = workInProgressHook;
325 | }
326 | } else {
327 | // mount时 后续的hook
328 | workInProgressHook.next = newHook;
329 | workInProgressHook = newHook;
330 | }
331 | return workInProgressHook;
332 | }
333 |
334 | function mountState(
335 | initialState: (() => State) | State
336 | ): [State, Dispatch] {
337 | // 找到当前useState对应的hook数据
338 | const hook = mountWorkInProgressHook();
339 | let memoizedState;
340 | if (initialState instanceof Function) {
341 | memoizedState = initialState();
342 | } else {
343 | memoizedState = initialState;
344 | }
345 | const queue = createFCUpdateQueue();
346 | hook.updateQueue = queue;
347 | hook.memoizedState = memoizedState;
348 | hook.baseState = memoizedState;
349 |
350 | // @ts-ignore
351 | const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
352 | queue.dispatch = dispatch;
353 | queue.lastRenderedState = memoizedState;
354 | return [memoizedState, dispatch];
355 | }
356 |
357 | function mountTransition(): [boolean, (callback: () => void) => void] {
358 | const [isPending, setPending] = mountState(false);
359 | const hook = mountWorkInProgressHook();
360 | const start = startTransition.bind(null, setPending);
361 | hook.memoizedState = start;
362 | return [isPending, start];
363 | }
364 |
365 | function updateTransition(): [boolean, (callback: () => void) => void] {
366 | const [isPending] = updateState();
367 | const hook = updateWorkInProgressHook();
368 | const start = hook.memoizedState;
369 | return [isPending as boolean, start];
370 | }
371 |
372 | function mountRef(initialValue: T): { current: T } {
373 | const hook = mountWorkInProgressHook();
374 | const ref = { current: initialValue };
375 | hook.memoizedState = ref;
376 | return ref;
377 | }
378 |
379 | function updateRef(initialValue: T): { current: T } {
380 | const hook = updateWorkInProgressHook();
381 | return hook.memoizedState;
382 | }
383 |
384 | function startTransition(setPending: Dispatch, callback: () => void) {
385 | setPending(true);
386 | const prevTransition = currentBatchConfig.transition;
387 | currentBatchConfig.transition = 1;
388 |
389 | callback();
390 | setPending(false);
391 |
392 | currentBatchConfig.transition = prevTransition;
393 | }
394 |
395 | function dispatchSetState(
396 | fiber: FiberNode,
397 | updateQueue: FCUpdateQueue,
398 | action: Action
399 | ) {
400 | const lane = requestUpdateLane();
401 | const update = createUpdate(action, lane);
402 |
403 | // eager策略
404 | const current = fiber.alternate;
405 | if (
406 | fiber.lanes === NoLanes &&
407 | (current === null || current.lanes === NoLanes)
408 | ) {
409 | // 当前产生的update是这个fiber的第一个update
410 | // 1. 更新前的状态 2.计算状态的方法
411 | const currentState = updateQueue.lastRenderedState;
412 | const eagerState = basicStateReducer(currentState, action);
413 | update.hasEagerState = true;
414 | update.eagerState = eagerState;
415 |
416 | if (Object.is(currentState, eagerState)) {
417 | enqueueUpdate(updateQueue, update, fiber, NoLane);
418 | // 命中eagerState
419 | if (__DEV__) {
420 | console.warn('命中eagerState', fiber);
421 | }
422 | return;
423 | }
424 | }
425 |
426 | enqueueUpdate(updateQueue, update, fiber, lane);
427 |
428 | scheduleUpdateOnFiber(fiber, lane);
429 | }
430 |
431 | function mountWorkInProgressHook(): Hook {
432 | const hook: Hook = {
433 | memoizedState: null,
434 | updateQueue: null,
435 | next: null,
436 | baseQueue: null,
437 | baseState: null
438 | };
439 | if (workInProgressHook === null) {
440 | // mount时 第一个hook
441 | if (currentlyRenderingFiber === null) {
442 | throw new Error('请在函数组件内调用hook');
443 | } else {
444 | workInProgressHook = hook;
445 | currentlyRenderingFiber.memoizedState = workInProgressHook;
446 | }
447 | } else {
448 | // mount时 后续的hook
449 | workInProgressHook.next = hook;
450 | workInProgressHook = hook;
451 | }
452 | return workInProgressHook;
453 | }
454 |
455 | function use(usable: Usable): T {
456 | if (usable !== null && typeof usable === 'object') {
457 | if (typeof (usable as Thenable).then === 'function') {
458 | const thenable = usable as Thenable;
459 | return trackUsedThenable(thenable);
460 | } else if ((usable as ReactContext).$$typeof === REACT_CONTEXT_TYPE) {
461 | const context = usable as ReactContext;
462 | return readContext(context);
463 | }
464 | }
465 | throw new Error('不支持的use参数 ' + usable);
466 | }
467 |
468 | export function resetHooksOnUnwind(wip: FiberNode) {
469 | currentlyRenderingFiber = null;
470 | currentHook = null;
471 | workInProgressHook = null;
472 | }
473 |
474 | export function bailoutHook(wip: FiberNode, renderLane: Lane) {
475 | const current = wip.alternate as FiberNode;
476 | wip.updateQueue = current.updateQueue;
477 | wip.flags &= ~PassiveEffect;
478 |
479 | current.lanes = removeLanes(current.lanes, renderLane);
480 | }
481 |
482 | function mountCallback(callback: T, deps: HookDeps | undefined) {
483 | const hook = mountWorkInProgressHook();
484 | const nextDeps = deps === undefined ? null : deps;
485 | hook.memoizedState = [callback, nextDeps];
486 | return callback;
487 | }
488 |
489 | function updateCallback(callback: T, deps: HookDeps | undefined) {
490 | const hook = updateWorkInProgressHook();
491 | const nextDeps = deps === undefined ? null : deps;
492 | const prevState = hook.memoizedState;
493 |
494 | if (nextDeps !== null) {
495 | const prevDeps = prevState[1];
496 | if (areHookInputsEqual(nextDeps, prevDeps)) {
497 | return prevState[0];
498 | }
499 | }
500 | hook.memoizedState = [callback, nextDeps];
501 | return callback;
502 | }
503 |
504 | function mountMemo(nextCreate: () => T, deps: HookDeps | undefined) {
505 | const hook = mountWorkInProgressHook();
506 | const nextDeps = deps === undefined ? null : deps;
507 | const nextValue = nextCreate();
508 | hook.memoizedState = [nextValue, nextDeps];
509 | return nextValue;
510 | }
511 |
512 | function updateMemo(nextCreate: () => T, deps: HookDeps | undefined) {
513 | const hook = updateWorkInProgressHook();
514 | const nextDeps = deps === undefined ? null : deps;
515 | const prevState = hook.memoizedState;
516 |
517 | if (nextDeps !== null) {
518 | const prevDeps = prevState[1];
519 | if (areHookInputsEqual(nextDeps, prevDeps)) {
520 | return prevState[0];
521 | }
522 | }
523 | const nextValue = nextCreate();
524 | hook.memoizedState = [nextValue, nextDeps];
525 | return nextValue;
526 | }
527 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberLanes.ts:
--------------------------------------------------------------------------------
1 | import ReactCurrentBatchConfig from 'react/src/currentBatchConfig';
2 | import {
3 | unstable_getCurrentPriorityLevel,
4 | unstable_IdlePriority,
5 | unstable_ImmediatePriority,
6 | unstable_NormalPriority,
7 | unstable_UserBlockingPriority
8 | } from 'scheduler';
9 | import { FiberRootNode } from './fiber';
10 |
11 | export type Lane = number;
12 | export type Lanes = number;
13 |
14 | export const SyncLane = 0b00001;
15 | export const NoLane = 0b00000;
16 | export const NoLanes = 0b00000;
17 | export const InputContinuousLane = 0b00010;
18 | export const DefaultLane = 0b00100;
19 | export const TransitionLane = 0b01000;
20 | export const IdleLane = 0b10000;
21 |
22 | export function mergeLanes(laneA: Lane, laneB: Lane): Lanes {
23 | return laneA | laneB;
24 | }
25 |
26 | export function requestUpdateLane() {
27 | const isTransition = ReactCurrentBatchConfig.transition !== null;
28 | if (isTransition) {
29 | return TransitionLane;
30 | }
31 |
32 | // 从上下文环境中获取Scheduler优先级
33 | const currentSchedulerPriority = unstable_getCurrentPriorityLevel();
34 | const lane = schedulerPriorityToLane(currentSchedulerPriority);
35 | return lane;
36 | }
37 |
38 | export function getHighestPriorityLane(lanes: Lanes): Lane {
39 | return lanes & -lanes;
40 | }
41 |
42 | export function isSubsetOfLanes(set: Lanes, subset: Lane) {
43 | return (set & subset) === subset;
44 | }
45 |
46 | export function markRootFinished(root: FiberRootNode, lane: Lane) {
47 | root.pendingLanes &= ~lane;
48 |
49 | root.suspendedLanes = NoLanes;
50 | root.pingedLanes = NoLanes;
51 | }
52 |
53 | export function lanesToSchedulerPriority(lanes: Lanes) {
54 | const lane = getHighestPriorityLane(lanes);
55 |
56 | if (lane === SyncLane) {
57 | return unstable_ImmediatePriority;
58 | }
59 | if (lane === InputContinuousLane) {
60 | return unstable_UserBlockingPriority;
61 | }
62 | if (lane === DefaultLane) {
63 | return unstable_NormalPriority;
64 | }
65 | return unstable_IdlePriority;
66 | }
67 |
68 | export function schedulerPriorityToLane(schedulerPriority: number): Lane {
69 | if (schedulerPriority === unstable_ImmediatePriority) {
70 | return SyncLane;
71 | }
72 | if (schedulerPriority === unstable_UserBlockingPriority) {
73 | return InputContinuousLane;
74 | }
75 | if (schedulerPriority === unstable_NormalPriority) {
76 | return DefaultLane;
77 | }
78 | return NoLane;
79 | }
80 |
81 | export function markRootPinged(root: FiberRootNode, pingedLane: Lane) {
82 | root.pingedLanes |= root.suspendedLanes & pingedLane;
83 | }
84 |
85 | export function markRootSuspended(root: FiberRootNode, suspendedLane: Lane) {
86 | root.suspendedLanes |= suspendedLane;
87 | root.pingedLanes &= ~suspendedLane;
88 | }
89 |
90 | export function getNextLane(root: FiberRootNode): Lane {
91 | const pendingLanes = root.pendingLanes;
92 |
93 | if (pendingLanes === NoLanes) {
94 | return NoLane;
95 | }
96 | let nextLane = NoLane;
97 |
98 | // 排除掉挂起的lane
99 | const suspendedLanes = pendingLanes & ~root.suspendedLanes;
100 | if (suspendedLanes !== NoLanes) {
101 | nextLane = getHighestPriorityLane(suspendedLanes);
102 | } else {
103 | const pingedLanes = pendingLanes & root.pingedLanes;
104 | if (pingedLanes !== NoLanes) {
105 | nextLane = getHighestPriorityLane(pingedLanes);
106 | }
107 | }
108 | return nextLane;
109 | }
110 |
111 | export function includeSomeLanes(set: Lanes, subset: Lane | Lanes): boolean {
112 | return (set & subset) !== NoLanes;
113 | }
114 |
115 | export function removeLanes(set: Lanes, subet: Lanes | Lane): Lanes {
116 | return set & ~subet;
117 | }
118 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberReconciler.ts:
--------------------------------------------------------------------------------
1 | import { Container } from 'hostConfig';
2 | import {
3 | unstable_ImmediatePriority,
4 | unstable_runWithPriority
5 | } from 'scheduler';
6 | import { ReactElementType } from 'shared/ReactTypes';
7 | import { FiberNode, FiberRootNode } from './fiber';
8 | import { requestUpdateLane } from './fiberLanes';
9 | import {
10 | createUpdate,
11 | createUpdateQueue,
12 | enqueueUpdate,
13 | UpdateQueue
14 | } from './updateQueue';
15 | import { scheduleUpdateOnFiber } from './workLoop';
16 | import { HostRoot } from './workTags';
17 |
18 | export function createContainer(container: Container) {
19 | const hostRootFiber = new FiberNode(HostRoot, {}, null);
20 | const root = new FiberRootNode(container, hostRootFiber);
21 | hostRootFiber.updateQueue = createUpdateQueue();
22 | return root;
23 | }
24 |
25 | export function updateContainer(
26 | element: ReactElementType | null,
27 | root: FiberRootNode
28 | ) {
29 | unstable_runWithPriority(unstable_ImmediatePriority, () => {
30 | const hostRootFiber = root.current;
31 | const lane = requestUpdateLane();
32 | const update = createUpdate(element, lane);
33 | enqueueUpdate(
34 | hostRootFiber.updateQueue as UpdateQueue,
35 | update,
36 | hostRootFiber,
37 | lane
38 | );
39 | scheduleUpdateOnFiber(hostRootFiber, lane);
40 | });
41 | return element;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberThrow.ts:
--------------------------------------------------------------------------------
1 | import { Wakeable } from 'shared/ReactTypes';
2 | import { FiberNode, FiberRootNode } from './fiber';
3 | import { ShouldCapture } from './fiberFlags';
4 | import { Lane, Lanes, SyncLane, markRootPinged } from './fiberLanes';
5 | import { ensureRootIsScheduled, markRootUpdated } from './workLoop';
6 | import { getSuspenseHandler } from './suspenseContext';
7 |
8 | function attachPingListener(
9 | root: FiberRootNode,
10 | wakeable: Wakeable,
11 | lane: Lane
12 | ) {
13 | let pingCache = root.pingCache;
14 | let threadIDs: Set | undefined;
15 |
16 | // WeakMap{ wakeable: Set[lane1, lane2, ...]}
17 | if (pingCache === null) {
18 | threadIDs = new Set();
19 | pingCache = root.pingCache = new WeakMap, Set>();
20 | pingCache.set(wakeable, threadIDs);
21 | } else {
22 | threadIDs = pingCache.get(wakeable);
23 | if (threadIDs === undefined) {
24 | threadIDs = new Set();
25 | pingCache.set(wakeable, threadIDs);
26 | }
27 | }
28 | if (!threadIDs.has(lane)) {
29 | // 第一次进入
30 | threadIDs.add(lane);
31 |
32 | // eslint-disable-next-line no-inner-declarations
33 | function ping() {
34 | if (pingCache !== null) {
35 | pingCache.delete(wakeable);
36 | }
37 | markRootUpdated(root, lane);
38 | markRootPinged(root, lane);
39 | ensureRootIsScheduled(root);
40 | }
41 | wakeable.then(ping, ping);
42 | }
43 | }
44 |
45 | export function throwException(root: FiberRootNode, value: any, lane: Lane) {
46 | if (
47 | value !== null &&
48 | typeof value === 'object' &&
49 | typeof value.then === 'function'
50 | ) {
51 | const weakable: Wakeable = value;
52 |
53 | const suspenseBoundary = getSuspenseHandler();
54 | if (suspenseBoundary) {
55 | suspenseBoundary.flags |= ShouldCapture;
56 | }
57 | attachPingListener(root, weakable, lane);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/fiberUnwindWork.ts:
--------------------------------------------------------------------------------
1 | import { FiberNode } from './fiber';
2 | import { popProvider } from './fiberContext';
3 | import { DidCapture, NoFlags, ShouldCapture } from './fiberFlags';
4 | import { popSuspenseHandler } from './suspenseContext';
5 | import { ContextProvider, HostRoot, SuspenseComponent } from './workTags';
6 |
7 | export function unwindWork(wip: FiberNode) {
8 | const flags = wip.flags;
9 | switch (wip.tag) {
10 | case SuspenseComponent:
11 | popSuspenseHandler();
12 | if (
13 | (flags & ShouldCapture) !== NoFlags &&
14 | (flags & DidCapture) === NoFlags
15 | ) {
16 | wip.flags = (flags & ~ShouldCapture) | DidCapture;
17 | return wip;
18 | }
19 | return null;
20 |
21 | case ContextProvider:
22 | const context = wip.type._context;
23 | popProvider(context);
24 | return null;
25 | default:
26 | return null;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/hookEffectTags.ts:
--------------------------------------------------------------------------------
1 | export const Passive = 0b0010;
2 | export const HookHasEffect = 0b0001;
3 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/reconciler.d.ts:
--------------------------------------------------------------------------------
1 | declare let __DEV__: boolean;
2 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/suspenseContext.ts:
--------------------------------------------------------------------------------
1 | import { FiberNode } from './fiber';
2 |
3 | const suspenseHandlerStack: FiberNode[] = [];
4 |
5 | export function getSuspenseHandler() {
6 | return suspenseHandlerStack[suspenseHandlerStack.length - 1];
7 | }
8 |
9 | export function pushSuspenseHandler(handler: FiberNode) {
10 | suspenseHandlerStack.push(handler);
11 | }
12 |
13 | export function popSuspenseHandler() {
14 | suspenseHandlerStack.pop();
15 | }
16 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/syncTaskQueue.ts:
--------------------------------------------------------------------------------
1 | let syncQueue: ((...args: any) => void)[] | null = null;
2 | let isFlushingSyncQueue = false;
3 |
4 | export function scheduleSyncCallback(callback: (...args: any) => void) {
5 | if (syncQueue === null) {
6 | syncQueue = [callback];
7 | } else {
8 | syncQueue.push(callback);
9 | }
10 | }
11 |
12 | export function flushSyncCallbacks() {
13 | if (!isFlushingSyncQueue && syncQueue) {
14 | isFlushingSyncQueue = true;
15 | try {
16 | syncQueue.forEach((callback) => callback());
17 | } catch (e) {
18 | if (__DEV__) {
19 | console.error('flushSyncCallbacks报错', e);
20 | }
21 | } finally {
22 | isFlushingSyncQueue = false;
23 | syncQueue = null;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/thenable.ts:
--------------------------------------------------------------------------------
1 | import {
2 | FulfilledThenable,
3 | PendingThenable,
4 | RejectedThenable,
5 | Thenable
6 | } from 'shared/ReactTypes';
7 |
8 | export const SuspenseException = new Error(
9 | '这不是个真实的错误,而是Suspense工作的一部分。如果你捕获到这个错误,请将它继续抛出去'
10 | );
11 |
12 | let suspendedThenable: Thenable | null = null;
13 |
14 | export function getSuspenseThenable(): Thenable {
15 | if (suspendedThenable === null) {
16 | throw new Error('应该存在suspendedThenable,这是个bug');
17 | }
18 | const thenable = suspendedThenable;
19 | suspendedThenable = null;
20 | return thenable;
21 | }
22 |
23 | // eslint-disable-next-line @typescript-eslint/no-empty-function
24 | function noop() {}
25 |
26 | export function trackUsedThenable(thenable: Thenable) {
27 | switch (thenable.status) {
28 | // 需要自己定义
29 | case 'fulfilled':
30 | return thenable.value;
31 | // 需要自己定义
32 | case 'rejected':
33 | throw thenable.reason;
34 | default:
35 | if (typeof thenable.status === 'string') {
36 | thenable.then(noop, noop);
37 | } else {
38 | // untracked
39 | const pending = thenable as unknown as PendingThenable;
40 | pending.status = 'pending';
41 | pending.then(
42 | (val) => {
43 | if (pending.status === 'pending') {
44 | // @ts-ignore
45 | const fulfilled: FulfilledThenable = pending;
46 | fulfilled.status = 'fulfilled';
47 | fulfilled.value = val;
48 | }
49 | },
50 | (err) => {
51 | if (pending.status === 'pending') {
52 | // @ts-ignore
53 | const rejected: RejectedThenable = pending;
54 | rejected.reason = err;
55 | rejected.status = 'rejected';
56 | }
57 | }
58 | );
59 | }
60 | }
61 | suspendedThenable = thenable;
62 | throw SuspenseException;
63 | }
64 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/updateQueue.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from 'react/src/currentDispatcher';
2 | import { Action } from 'shared/ReactTypes';
3 | import { isSubsetOfLanes, Lane, mergeLanes, NoLane } from './fiberLanes';
4 | import { FiberNode } from './fiber';
5 |
6 | export interface Update {
7 | action: Action;
8 | lane: Lane;
9 | next: Update | null;
10 | hasEagerState: boolean;
11 | eagerState: State | null;
12 | }
13 |
14 | export interface UpdateQueue {
15 | shared: {
16 | pending: Update | null;
17 | };
18 | dispatch: Dispatch | null;
19 | }
20 |
21 | export const createUpdate = (
22 | action: Action,
23 | lane: Lane,
24 | hasEagerState = false,
25 | eagerState = null
26 | ): Update => {
27 | return {
28 | action,
29 | lane,
30 | next: null,
31 | hasEagerState,
32 | eagerState
33 | };
34 | };
35 |
36 | export const createUpdateQueue = () => {
37 | return {
38 | shared: {
39 | pending: null
40 | },
41 | dispatch: null
42 | } as UpdateQueue;
43 | };
44 |
45 | export const enqueueUpdate = (
46 | updateQueue: UpdateQueue,
47 | update: Update,
48 | fiber: FiberNode,
49 | lane: Lane
50 | ) => {
51 | const pending = updateQueue.shared.pending;
52 | if (pending === null) {
53 | // pending = a -> a
54 | update.next = update;
55 | } else {
56 | // pending = b -> a -> b
57 | // pending = c -> a -> b -> c
58 | update.next = pending.next;
59 | pending.next = update;
60 | }
61 | updateQueue.shared.pending = update;
62 |
63 | fiber.lanes = mergeLanes(fiber.lanes, lane);
64 | const alternate = fiber.alternate;
65 | if (alternate !== null) {
66 | alternate.lanes = mergeLanes(alternate.lanes, lane);
67 | }
68 | };
69 |
70 | export function basicStateReducer(
71 | state: State,
72 | action: Action
73 | ): State {
74 | if (action instanceof Function) {
75 | // baseState 1 update (x) => 4x -> memoizedState 4
76 | return action(state);
77 | } else {
78 | // baseState 1 update 2 -> memoizedState 2
79 | return action;
80 | }
81 | }
82 |
83 | export const processUpdateQueue = (
84 | baseState: State,
85 | pendingUpdate: Update | null,
86 | renderLane: Lane,
87 | onSkipUpdate?: (update: Update) => void
88 | ): {
89 | memoizedState: State;
90 | baseState: State;
91 | baseQueue: Update | null;
92 | } => {
93 | const result: ReturnType> = {
94 | memoizedState: baseState,
95 | baseState,
96 | baseQueue: null
97 | };
98 |
99 | if (pendingUpdate !== null) {
100 | // 第一个update
101 | const first = pendingUpdate.next;
102 | let pending = pendingUpdate.next as Update;
103 |
104 | let newBaseState = baseState;
105 | let newBaseQueueFirst: Update | null = null;
106 | let newBaseQueueLast: Update | null = null;
107 | let newState = baseState;
108 |
109 | do {
110 | const updateLane = pending.lane;
111 | if (!isSubsetOfLanes(renderLane, updateLane)) {
112 | // 优先级不够 被跳过
113 | const clone = createUpdate(pending.action, pending.lane);
114 |
115 | onSkipUpdate?.(clone);
116 |
117 | // 是不是第一个被跳过的
118 | if (newBaseQueueFirst === null) {
119 | // first u0 last = u0
120 | newBaseQueueFirst = clone;
121 | newBaseQueueLast = clone;
122 | newBaseState = newState;
123 | } else {
124 | // first u0 -> u1 -> u2
125 | // last u2
126 | (newBaseQueueLast as Update).next = clone;
127 | newBaseQueueLast = clone;
128 | }
129 | } else {
130 | // 优先级足够
131 | if (newBaseQueueLast !== null) {
132 | const clone = createUpdate(pending.action, NoLane);
133 | newBaseQueueLast.next = clone;
134 | newBaseQueueLast = clone;
135 | }
136 |
137 | const action = pending.action;
138 | if (pending.hasEagerState) {
139 | newState = pending.eagerState;
140 | } else {
141 | newState = basicStateReducer(baseState, action);
142 | }
143 | }
144 | pending = pending.next as Update;
145 | } while (pending !== first);
146 |
147 | if (newBaseQueueLast === null) {
148 | // 本次计算没有update被跳过
149 | newBaseState = newState;
150 | } else {
151 | newBaseQueueLast.next = newBaseQueueFirst;
152 | }
153 | result.memoizedState = newState;
154 | result.baseState = newBaseState;
155 | result.baseQueue = newBaseQueueLast;
156 | }
157 | return result;
158 | };
159 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/workLoop.ts:
--------------------------------------------------------------------------------
1 | import { scheduleMicroTask } from 'hostConfig';
2 | import { beginWork } from './beginWork';
3 | import {
4 | commitHookEffectListCreate,
5 | commitHookEffectListDestroy,
6 | commitHookEffectListUnmount,
7 | commitLayoutEffects,
8 | commitMutationEffects
9 | } from './commitWork';
10 | import { completeWork } from './completeWork';
11 | import {
12 | createWorkInProgress,
13 | FiberNode,
14 | FiberRootNode,
15 | PendingPassiveEffects
16 | } from './fiber';
17 | import {
18 | HostEffectMask,
19 | MutationMask,
20 | NoFlags,
21 | PassiveMask
22 | } from './fiberFlags';
23 | import {
24 | getHighestPriorityLane,
25 | getNextLane,
26 | Lane,
27 | lanesToSchedulerPriority,
28 | markRootFinished,
29 | markRootSuspended,
30 | mergeLanes,
31 | NoLane,
32 | SyncLane
33 | } from './fiberLanes';
34 | import { flushSyncCallbacks, scheduleSyncCallback } from './syncTaskQueue';
35 | import { HostRoot } from './workTags';
36 | import {
37 | unstable_scheduleCallback as scheduleCallback,
38 | unstable_NormalPriority as NormalPriority,
39 | unstable_shouldYield,
40 | unstable_cancelCallback
41 | } from 'scheduler';
42 | import { HookHasEffect, Passive } from './hookEffectTags';
43 | import { throwException } from './fiberThrow';
44 | import { SuspenseException, getSuspenseThenable } from './thenable';
45 | import { unwindWork } from './fiberUnwindWork';
46 | import { resetHooksOnUnwind } from './fiberHooks';
47 |
48 | let workInProgress: FiberNode | null = null;
49 | let wipRootRenderLane: Lane = NoLane;
50 | let rootDoesHasPassiveEffects = false;
51 |
52 | type RootExitStatus = number;
53 | // 工作中的状态
54 | const RootInProgress = 0;
55 | // 并发中间状态
56 | const RootInComplete = 1;
57 | // 完成状态
58 | const RootCompleted = 2;
59 | // 未完成状态,不用进入commit阶段
60 | const RootDidNotComplete = 3;
61 | let workInProgressRootExitStatus: number = RootInProgress;
62 |
63 | // Suspense
64 | type SuspendedReason =
65 | | typeof NotSuspended
66 | | typeof SuspendedOnError
67 | | typeof SuspendedOnData
68 | | typeof SuspendedOnDeprecatedThrowPromise;
69 | const NotSuspended = 0;
70 | const SuspendedOnError = 1;
71 | const SuspendedOnData = 2;
72 | const SuspendedOnDeprecatedThrowPromise = 4;
73 |
74 | let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
75 | let workInProgressThrownValue: any = null;
76 |
77 | // TODO 执行过程中报错了
78 |
79 | function prepareFreshStack(root: FiberRootNode, lane: Lane) {
80 | root.finishedLane = NoLane;
81 | root.finishedWork = null;
82 | workInProgress = createWorkInProgress(root.current, {});
83 | wipRootRenderLane = lane;
84 |
85 | workInProgressRootExitStatus = RootInProgress;
86 | workInProgressSuspendedReason = NotSuspended;
87 | workInProgressThrownValue = null;
88 | }
89 |
90 | export function scheduleUpdateOnFiber(fiber: FiberNode, lane: Lane) {
91 | // fiberRootNode
92 | const root = markUpdateLaneFromFiberToRoot(fiber, lane);
93 | markRootUpdated(root, lane);
94 | ensureRootIsScheduled(root);
95 | }
96 |
97 | // schedule阶段入口
98 | export function ensureRootIsScheduled(root: FiberRootNode) {
99 | const updateLane = getNextLane(root);
100 | const existingCallback = root.callbackNode;
101 |
102 | if (updateLane === NoLane) {
103 | if (existingCallback !== null) {
104 | unstable_cancelCallback(existingCallback);
105 | }
106 | root.callbackNode = null;
107 | root.callbackPriority = NoLane;
108 | return;
109 | }
110 |
111 | const curPriority = updateLane;
112 | const prevPriority = root.callbackPriority;
113 |
114 | if (curPriority === prevPriority) {
115 | return;
116 | }
117 |
118 | if (existingCallback !== null) {
119 | unstable_cancelCallback(existingCallback);
120 | }
121 | let newCallbackNode = null;
122 |
123 | if (__DEV__) {
124 | console.log(
125 | `在${updateLane === SyncLane ? '微' : '宏'}任务中调度,优先级:`,
126 | updateLane
127 | );
128 | }
129 |
130 | if (updateLane === SyncLane) {
131 | // 同步优先级 用微任务调度
132 | // [performSyncWorkOnRoot, performSyncWorkOnRoot, performSyncWorkOnRoot]
133 | scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root, updateLane));
134 | scheduleMicroTask(flushSyncCallbacks);
135 | } else {
136 | // 其他优先级 用宏任务调度
137 | const schedulerPriority = lanesToSchedulerPriority(updateLane);
138 |
139 | newCallbackNode = scheduleCallback(
140 | schedulerPriority,
141 | // @ts-ignore
142 | performConcurrentWorkOnRoot.bind(null, root)
143 | );
144 | }
145 | root.callbackNode = newCallbackNode;
146 | root.callbackPriority = curPriority;
147 | }
148 |
149 | export function markRootUpdated(root: FiberRootNode, lane: Lane) {
150 | root.pendingLanes = mergeLanes(root.pendingLanes, lane);
151 | }
152 |
153 | export function markUpdateLaneFromFiberToRoot(fiber: FiberNode, lane: Lane) {
154 | let node = fiber;
155 | let parent = node.return;
156 | while (parent !== null) {
157 | parent.childLanes = mergeLanes(parent.childLanes, lane);
158 | const alternate = parent.alternate;
159 | if (alternate !== null) {
160 | alternate.childLanes = mergeLanes(alternate.childLanes, lane);
161 | }
162 |
163 | node = parent;
164 | parent = node.return;
165 | }
166 | if (node.tag === HostRoot) {
167 | return node.stateNode;
168 | }
169 | return null;
170 | }
171 |
172 | function performConcurrentWorkOnRoot(
173 | root: FiberRootNode,
174 | didTimeout: boolean
175 | ): any {
176 | // 保证useEffect回调执行
177 | const curCallback = root.callbackNode;
178 | const didFlushPassiveEffect = flushPassiveEffects(root.pendingPassiveEffects);
179 | if (didFlushPassiveEffect) {
180 | if (root.callbackNode !== curCallback) {
181 | return null;
182 | }
183 | }
184 |
185 | const lane = getNextLane(root);
186 | const curCallbackNode = root.callbackNode;
187 | if (lane === NoLane) {
188 | return null;
189 | }
190 | const needSync = lane === SyncLane || didTimeout;
191 | // render阶段
192 | const exitStatus = renderRoot(root, lane, !needSync);
193 |
194 | switch (exitStatus) {
195 | // 中断
196 | case RootInComplete:
197 | if (root.callbackNode !== curCallbackNode) {
198 | return null;
199 | }
200 | return performConcurrentWorkOnRoot.bind(null, root);
201 | case RootCompleted:
202 | const finishedWork = root.current.alternate;
203 | root.finishedWork = finishedWork;
204 | root.finishedLane = lane;
205 | wipRootRenderLane = NoLane;
206 | commitRoot(root);
207 | break;
208 | case RootDidNotComplete:
209 | markRootSuspended(root, lane);
210 | wipRootRenderLane = NoLane;
211 | ensureRootIsScheduled(root);
212 | break;
213 | default:
214 | if (__DEV__) {
215 | console.error('还未实现的并发更新结束状态');
216 | }
217 | }
218 | }
219 |
220 | function performSyncWorkOnRoot(root: FiberRootNode) {
221 | const nextLane = getNextLane(root);
222 |
223 | if (nextLane !== SyncLane) {
224 | // 其他比SyncLane低的优先级
225 | // NoLane
226 | ensureRootIsScheduled(root);
227 | return;
228 | }
229 |
230 | const exitStatus = renderRoot(root, nextLane, false);
231 |
232 | switch (exitStatus) {
233 | case RootCompleted:
234 | const finishedWork = root.current.alternate;
235 | root.finishedWork = finishedWork;
236 | root.finishedLane = nextLane;
237 | wipRootRenderLane = NoLane;
238 | commitRoot(root);
239 | break;
240 | case RootDidNotComplete:
241 | wipRootRenderLane = NoLane;
242 | markRootSuspended(root, nextLane);
243 | ensureRootIsScheduled(root);
244 | break;
245 | default:
246 | if (__DEV__) {
247 | console.error('还未实现的同步更新结束状态');
248 | }
249 | break;
250 | }
251 | }
252 |
253 | let c = 0;
254 |
255 | function renderRoot(root: FiberRootNode, lane: Lane, shouldTimeSlice: boolean) {
256 | if (__DEV__) {
257 | console.log(`开始${shouldTimeSlice ? '并发' : '同步'}更新`, root);
258 | }
259 |
260 | if (wipRootRenderLane !== lane) {
261 | // 初始化
262 | prepareFreshStack(root, lane);
263 | }
264 |
265 | do {
266 | try {
267 | if (
268 | workInProgressSuspendedReason !== NotSuspended &&
269 | workInProgress !== null
270 | ) {
271 | const thrownValue = workInProgressThrownValue;
272 |
273 | workInProgressSuspendedReason = NotSuspended;
274 | workInProgressThrownValue = null;
275 |
276 | throwAndUnwindWorkLoop(root, workInProgress, thrownValue, lane);
277 | }
278 |
279 | shouldTimeSlice ? workLoopConcurrent() : workLoopSync();
280 | break;
281 | } catch (e) {
282 | if (__DEV__) {
283 | console.warn('workLoop发生错误', e);
284 | }
285 | c++;
286 | if (c > 20) {
287 | break;
288 | console.warn('break!');
289 | }
290 | handleThrow(root, e);
291 | }
292 | } while (true);
293 |
294 | if (workInProgressRootExitStatus !== RootInProgress) {
295 | return workInProgressRootExitStatus;
296 | }
297 |
298 | // 中断执行
299 | if (shouldTimeSlice && workInProgress !== null) {
300 | return RootInComplete;
301 | }
302 | // render阶段执行完
303 | if (!shouldTimeSlice && workInProgress !== null && __DEV__) {
304 | console.error(`render阶段结束时wip不应该不是null`);
305 | }
306 | return RootCompleted;
307 | }
308 |
309 | function commitRoot(root: FiberRootNode) {
310 | const finishedWork = root.finishedWork;
311 |
312 | if (finishedWork === null) {
313 | return;
314 | }
315 |
316 | if (__DEV__) {
317 | console.warn('commit阶段开始', finishedWork);
318 | }
319 | const lane = root.finishedLane;
320 |
321 | if (lane === NoLane && __DEV__) {
322 | console.error('commit阶段finishedLane不应该是NoLane');
323 | }
324 |
325 | // 重置
326 | root.finishedWork = null;
327 | root.finishedLane = NoLane;
328 |
329 | markRootFinished(root, lane);
330 |
331 | if (
332 | (finishedWork.flags & PassiveMask) !== NoFlags ||
333 | (finishedWork.subtreeFlags & PassiveMask) !== NoFlags
334 | ) {
335 | if (!rootDoesHasPassiveEffects) {
336 | rootDoesHasPassiveEffects = true;
337 | // 调度副作用
338 | scheduleCallback(NormalPriority, () => {
339 | // 执行副作用
340 | flushPassiveEffects(root.pendingPassiveEffects);
341 | return;
342 | });
343 | }
344 | }
345 |
346 | // 判断是否存在3个子阶段需要执行的操作
347 | // root flags root subtreeFlags
348 | const subtreeHasEffect =
349 | (finishedWork.subtreeFlags & (MutationMask | PassiveMask)) !== NoFlags;
350 | const rootHasEffect =
351 | (finishedWork.flags & (MutationMask | PassiveMask)) !== NoFlags;
352 |
353 | if (subtreeHasEffect || rootHasEffect) {
354 | // beforeMutation
355 | // mutation Placement
356 | commitMutationEffects(finishedWork, root);
357 |
358 | root.current = finishedWork;
359 |
360 | // 阶段3/3:Layout
361 | commitLayoutEffects(finishedWork, root);
362 | } else {
363 | root.current = finishedWork;
364 | }
365 |
366 | rootDoesHasPassiveEffects = false;
367 | ensureRootIsScheduled(root);
368 | }
369 |
370 | function flushPassiveEffects(pendingPassiveEffects: PendingPassiveEffects) {
371 | let didFlushPassiveEffect = false;
372 | pendingPassiveEffects.unmount.forEach((effect) => {
373 | didFlushPassiveEffect = true;
374 | commitHookEffectListUnmount(Passive, effect);
375 | });
376 | pendingPassiveEffects.unmount = [];
377 |
378 | pendingPassiveEffects.update.forEach((effect) => {
379 | didFlushPassiveEffect = true;
380 | commitHookEffectListDestroy(Passive | HookHasEffect, effect);
381 | });
382 | pendingPassiveEffects.update.forEach((effect) => {
383 | didFlushPassiveEffect = true;
384 | commitHookEffectListCreate(Passive | HookHasEffect, effect);
385 | });
386 | pendingPassiveEffects.update = [];
387 | flushSyncCallbacks();
388 | return didFlushPassiveEffect;
389 | }
390 |
391 | function workLoopSync() {
392 | while (workInProgress !== null) {
393 | performUnitOfWork(workInProgress);
394 | }
395 | }
396 | function workLoopConcurrent() {
397 | while (workInProgress !== null && !unstable_shouldYield()) {
398 | performUnitOfWork(workInProgress);
399 | }
400 | }
401 |
402 | function performUnitOfWork(fiber: FiberNode) {
403 | const next = beginWork(fiber, wipRootRenderLane);
404 | fiber.memoizedProps = fiber.pendingProps;
405 |
406 | if (next === null) {
407 | completeUnitOfWork(fiber);
408 | } else {
409 | workInProgress = next;
410 | }
411 | }
412 |
413 | function completeUnitOfWork(fiber: FiberNode) {
414 | let node: FiberNode | null = fiber;
415 |
416 | do {
417 | completeWork(node);
418 | const sibling = node.sibling;
419 |
420 | if (sibling !== null) {
421 | workInProgress = sibling;
422 | return;
423 | }
424 | node = node.return;
425 | workInProgress = node;
426 | } while (node !== null);
427 | }
428 |
429 | function handleThrow(root: FiberRootNode, thrownValue: any): void {
430 | /*
431 | throw可能的情况
432 | 1. use thenable
433 | 2. error (Error Boundary处理)
434 | */
435 | if (thrownValue === SuspenseException) {
436 | workInProgressSuspendedReason = SuspendedOnData;
437 | thrownValue = getSuspenseThenable();
438 | } else {
439 | const isWakeable =
440 | thrownValue !== null &&
441 | typeof thrownValue === 'object' &&
442 | typeof thrownValue.then === 'function';
443 |
444 | workInProgressThrownValue = thrownValue;
445 | workInProgressSuspendedReason = isWakeable
446 | ? SuspendedOnDeprecatedThrowPromise
447 | : SuspendedOnError;
448 | }
449 | workInProgressThrownValue = thrownValue;
450 | }
451 |
452 | function throwAndUnwindWorkLoop(
453 | root: FiberRootNode,
454 | unitOfWork: FiberNode,
455 | thrownValue: any,
456 | lane: Lane
457 | ) {
458 | // unwind前的重置hook,避免 hook0 use hook1 时 use造成中断,再恢复时前后hook对应不上
459 | resetHooksOnUnwind(unitOfWork);
460 | throwException(root, thrownValue, lane);
461 | unwindUnitOfWork(unitOfWork);
462 | }
463 |
464 | function unwindUnitOfWork(unitOfWork: FiberNode) {
465 | let incompleteWork: FiberNode | null = unitOfWork;
466 | do {
467 | const next = unwindWork(incompleteWork);
468 |
469 | if (next !== null) {
470 | next.flags &= HostEffectMask;
471 | workInProgress = next;
472 | return;
473 | }
474 |
475 | const returnFiber = incompleteWork.return as FiberNode;
476 | if (returnFiber !== null) {
477 | returnFiber.deletions = null;
478 | }
479 | incompleteWork = returnFiber;
480 | // workInProgress = incompleteWork;
481 | } while (incompleteWork !== null);
482 |
483 | // 没有 边界 中止unwind流程,一直到root
484 | workInProgress = null;
485 | workInProgressRootExitStatus = RootDidNotComplete;
486 | }
487 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/workTags.ts:
--------------------------------------------------------------------------------
1 | export type WorkTag =
2 | | typeof FunctionComponent
3 | | typeof HostRoot
4 | | typeof HostComponent
5 | | typeof HostText
6 | | typeof Fragment
7 | | typeof ContextProvider
8 | | typeof SuspenseComponent
9 | | typeof OffscreenComponent
10 | | typeof LazyComponent
11 | | typeof MemoComponent;
12 |
13 | export const FunctionComponent = 0;
14 | export const HostRoot = 3;
15 |
16 | export const HostComponent = 5;
17 | // 123
18 | export const HostText = 6;
19 | export const Fragment = 7;
20 | export const ContextProvider = 8;
21 |
22 | export const SuspenseComponent = 13;
23 | export const OffscreenComponent = 14;
24 |
25 | export const LazyComponent = 16;
26 | export const MemoComponent = 15;
27 |
--------------------------------------------------------------------------------
/packages/react/index.ts:
--------------------------------------------------------------------------------
1 | import { Dispatcher, resolveDispatcher } from './src/currentDispatcher';
2 | import currentDispatcher from './src/currentDispatcher';
3 | import currentBatchConfig from './src/currentBatchConfig';
4 | import {
5 | createElement as createElementFn,
6 | isValidElement as isValidElementFn
7 | } from './src/jsx';
8 | export { REACT_FRAGMENT_TYPE as Fragment } from 'shared/ReactSymbols';
9 | export { createContext } from './src/context';
10 | export { lazy } from './src/lazy';
11 | export { REACT_SUSPENSE_TYPE as Suspense } from 'shared/ReactSymbols';
12 | export { memo } from './src/memo';
13 | // React
14 |
15 | export const useState: Dispatcher['useState'] = (initialState) => {
16 | const dispatcher = resolveDispatcher();
17 | return dispatcher.useState(initialState);
18 | };
19 |
20 | export const useEffect: Dispatcher['useEffect'] = (create, deps) => {
21 | const dispatcher = resolveDispatcher();
22 | return dispatcher.useEffect(create, deps);
23 | };
24 |
25 | export const useTransition: Dispatcher['useTransition'] = () => {
26 | const dispatcher = resolveDispatcher();
27 | return dispatcher.useTransition();
28 | };
29 |
30 | export const useRef: Dispatcher['useRef'] = (initialValue) => {
31 | const dispatcher = resolveDispatcher() as Dispatcher;
32 | return dispatcher.useRef(initialValue);
33 | };
34 |
35 | export const useContext: Dispatcher['useContext'] = (context) => {
36 | const dispatcher = resolveDispatcher() as Dispatcher;
37 | return dispatcher.useContext(context);
38 | };
39 |
40 | export const use: Dispatcher['use'] = (usable) => {
41 | const dispatcher = resolveDispatcher() as Dispatcher;
42 | return dispatcher.use(usable);
43 | };
44 |
45 | export const useMemo: Dispatcher['useMemo'] = (nextCreate, deps) => {
46 | const dispatcher = resolveDispatcher() as Dispatcher;
47 | return dispatcher.useMemo(nextCreate, deps);
48 | };
49 |
50 | export const useCallback: Dispatcher['useCallback'] = (callback, deps) => {
51 | const dispatcher = resolveDispatcher() as Dispatcher;
52 | return dispatcher.useCallback(callback, deps);
53 | };
54 |
55 | // 内部数据共享层
56 | export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
57 | currentDispatcher,
58 | currentBatchConfig
59 | };
60 |
61 | export const version = '0.0.0';
62 |
63 | // TODO 根据环境区分使用jsx/jsxDEV
64 | export const createElement = createElementFn;
65 | export const isValidElement = isValidElementFn;
66 |
--------------------------------------------------------------------------------
/packages/react/jsx-dev-runtime.ts:
--------------------------------------------------------------------------------
1 | export { jsxDEV, Fragment } from './src/jsx';
2 |
--------------------------------------------------------------------------------
/packages/react/node_modules/shared:
--------------------------------------------------------------------------------
1 | ../../shared
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "version": "1.0.0",
4 | "description": "react公用方法",
5 | "module": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace:*"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react/src/__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 | });
271 |
--------------------------------------------------------------------------------
/packages/react/src/context.ts:
--------------------------------------------------------------------------------
1 | import { REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE } from 'shared/ReactSymbols';
2 | import { ReactContext } from 'shared/ReactTypes';
3 |
4 | export function createContext(defaultValue: T): ReactContext {
5 | const context: ReactContext = {
6 | $$typeof: REACT_CONTEXT_TYPE,
7 | Provider: null,
8 | _currentValue: defaultValue
9 | };
10 | context.Provider = {
11 | $$typeof: REACT_PROVIDER_TYPE,
12 | _context: context
13 | };
14 | return context;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/react/src/currentBatchConfig.ts:
--------------------------------------------------------------------------------
1 | interface BatchConfig {
2 | transition: number | null;
3 | }
4 |
5 | const ReactCurrentBatchConfig: BatchConfig = {
6 | transition: null
7 | };
8 |
9 | export default ReactCurrentBatchConfig;
10 |
--------------------------------------------------------------------------------
/packages/react/src/currentDispatcher.ts:
--------------------------------------------------------------------------------
1 | import { HookDeps } from 'react-reconciler/src/fiberHooks';
2 | import { Action, ReactContext, Usable } from 'shared/ReactTypes';
3 |
4 | export interface Dispatcher {
5 | useState: (initialState: (() => T) | T) => [T, Dispatch];
6 | useEffect: (callback: () => void | void, deps: HookDeps | undefined) => void;
7 | useTransition: () => [boolean, (callback: () => void) => void];
8 | useRef: (initialValue: T) => { current: T };
9 | useContext: (context: ReactContext) => T;
10 | use: (usable: Usable) => T;
11 | useMemo: (nextCreate: () => T, deps: HookDeps | undefined) => T;
12 | useCallback: (callback: T, deps: HookDeps | undefined) => T;
13 | }
14 |
15 | export type Dispatch = (action: Action) => void;
16 |
17 | const currentDispatcher: { current: Dispatcher | null } = {
18 | current: null
19 | };
20 |
21 | export const resolveDispatcher = (): Dispatcher => {
22 | const dispatcher = currentDispatcher.current;
23 |
24 | if (dispatcher === null) {
25 | throw new Error('hook只能在函数组件中执行');
26 | }
27 | return dispatcher;
28 | };
29 |
30 | export default currentDispatcher;
31 |
--------------------------------------------------------------------------------
/packages/react/src/jsx.ts:
--------------------------------------------------------------------------------
1 | import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols';
2 | import {
3 | Type,
4 | Key,
5 | Ref,
6 | Props,
7 | ReactElementType,
8 | ElementType
9 | } from 'shared/ReactTypes';
10 |
11 | // ReactElement
12 |
13 | const ReactElement = function (
14 | type: Type,
15 | key: Key,
16 | ref: Ref,
17 | props: Props
18 | ): ReactElementType {
19 | const element = {
20 | $$typeof: REACT_ELEMENT_TYPE,
21 | type,
22 | key,
23 | ref,
24 | props,
25 | __mark: 'KaSong'
26 | };
27 | return element;
28 | };
29 |
30 | export function isValidElement(object: any) {
31 | return (
32 | typeof object === 'object' &&
33 | object !== null &&
34 | object.$$typeof === REACT_ELEMENT_TYPE
35 | );
36 | }
37 |
38 | export const createElement = (
39 | type: ElementType,
40 | config: any,
41 | ...maybeChildren: any
42 | ) => {
43 | let key: Key = null;
44 | const props: Props = {};
45 | let ref: Ref = null;
46 |
47 | for (const prop in config) {
48 | const val = config[prop];
49 | if (prop === 'key') {
50 | if (val !== undefined) {
51 | key = '' + val;
52 | }
53 | continue;
54 | }
55 | if (prop === 'ref') {
56 | if (val !== undefined) {
57 | ref = val;
58 | }
59 | continue;
60 | }
61 | if ({}.hasOwnProperty.call(config, prop)) {
62 | props[prop] = val;
63 | }
64 | }
65 | const maybeChildrenLength = maybeChildren.length;
66 | if (maybeChildrenLength) {
67 | if (maybeChildrenLength === 1) {
68 | props.children = maybeChildren[0];
69 | } else {
70 | props.children = maybeChildren;
71 | }
72 | }
73 | return ReactElement(type, key, ref, props);
74 | };
75 |
76 | export const Fragment = REACT_FRAGMENT_TYPE;
77 |
78 | export const jsx = (type: ElementType, config: any, maybeKey: any) => {
79 | let key: Key = null;
80 | const props: Props = {};
81 | let ref: Ref = null;
82 |
83 | if (maybeKey !== undefined) {
84 | key = '' + maybeKey;
85 | }
86 |
87 | for (const prop in config) {
88 | const val = config[prop];
89 | if (prop === 'key') {
90 | if (val !== undefined) {
91 | key = '' + val;
92 | }
93 | continue;
94 | }
95 | if (prop === 'ref') {
96 | if (val !== undefined) {
97 | ref = val;
98 | }
99 | continue;
100 | }
101 | if ({}.hasOwnProperty.call(config, prop)) {
102 | props[prop] = val;
103 | }
104 | }
105 |
106 | return ReactElement(type, key, ref, props);
107 | };
108 |
109 | export const jsxDEV = jsx;
110 |
--------------------------------------------------------------------------------
/packages/react/src/lazy.ts:
--------------------------------------------------------------------------------
1 | import { Thenable, Wakeable } from 'shared/ReactTypes';
2 | import { REACT_LAZY_TYPE } from 'shared/ReactSymbols';
3 |
4 | const Uninitialized = -1;
5 | const Pending = 0;
6 | const Resolved = 1;
7 | const Rejected = 2;
8 |
9 | type UninitializedPayload = {
10 | _status: typeof Uninitialized;
11 | _result: () => Thenable<{ default: T }>;
12 | };
13 |
14 | type PendingPayload = {
15 | _status: typeof Pending;
16 | _result: Wakeable;
17 | };
18 |
19 | type ResolvedPayload = {
20 | _status: typeof Resolved;
21 | _result: { default: T };
22 | };
23 |
24 | type RejectedPayload = {
25 | _status: typeof Rejected;
26 | _result: any;
27 | };
28 |
29 | type Payload =
30 | | UninitializedPayload
31 | | PendingPayload
32 | | ResolvedPayload
33 | | RejectedPayload;
34 |
35 | export type LazyComponent = {
36 | $$typeof: symbol | number;
37 | _payload: P;
38 | _init: (payload: P) => T;
39 | };
40 |
41 | function lazyInitializer(payload: Payload): T {
42 | if (payload._status === Uninitialized) {
43 | const ctor = payload._result;
44 | const thenable = ctor();
45 | thenable.then(
46 | (moduleObject) => {
47 | // @ts-ignore
48 | const resolved: ResolvedPayload = payload;
49 | resolved._status = Resolved;
50 | resolved._result = moduleObject;
51 | },
52 | (error) => {
53 | // @ts-ignore
54 | const rejected: RejectedPayload = payload;
55 | rejected._status = Rejected;
56 | rejected._result = error;
57 | }
58 | );
59 | if (payload._status === Uninitialized) {
60 | // @ts-ignore
61 | const pending: PendingPayload = payload;
62 | pending._status = Pending;
63 | pending._result = thenable;
64 | }
65 | }
66 | if (payload._status === Resolved) {
67 | const moduleObject = payload._result;
68 | return moduleObject.default;
69 | } else {
70 | throw payload._result;
71 | }
72 | }
73 |
74 | export function lazy(
75 | ctor: () => Thenable<{ default: T }>
76 | ): LazyComponent> {
77 | const payload: Payload = {
78 | _status: Uninitialized,
79 | _result: ctor
80 | };
81 |
82 | const lazyType: LazyComponent> = {
83 | $$typeof: REACT_LAZY_TYPE,
84 | _payload: payload,
85 | _init: lazyInitializer
86 | };
87 |
88 | return lazyType;
89 | }
90 |
--------------------------------------------------------------------------------
/packages/react/src/memo.ts:
--------------------------------------------------------------------------------
1 | // React.memo(function App() {/** ... */})
2 |
3 | import { FiberNode } from 'react-reconciler/src/fiber';
4 | import { REACT_MEMO_TYPE } from 'shared/ReactSymbols';
5 | import { Props } from 'shared/ReactTypes';
6 |
7 | export function memo(
8 | type: FiberNode['type'],
9 | compare?: (oldProps: Props, newProps: Props) => boolean
10 | ) {
11 | const fiberType = {
12 | $$typeof: REACT_MEMO_TYPE,
13 | type,
14 | compare: compare === undefined ? null : compare
15 | };
16 | // memo fiber.type.type
17 | return fiberType;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/shared/ReactSymbols.ts:
--------------------------------------------------------------------------------
1 | const supportSymbol = typeof Symbol === 'function' && Symbol.for;
2 |
3 | export const REACT_ELEMENT_TYPE = supportSymbol
4 | ? Symbol.for('react.element')
5 | : 0xeac7;
6 |
7 | export const REACT_FRAGMENT_TYPE = supportSymbol
8 | ? Symbol.for('react.fragment')
9 | : 0xeaca;
10 |
11 | export const REACT_CONTEXT_TYPE = supportSymbol
12 | ? Symbol.for('react.context')
13 | : 0xeacc;
14 |
15 | export const REACT_PROVIDER_TYPE = supportSymbol
16 | ? Symbol.for('react.provider')
17 | : 0xeac2;
18 |
19 | export const REACT_SUSPENSE_TYPE = supportSymbol
20 | ? Symbol.for('react.suspense')
21 | : 0xead1;
22 |
23 | export const REACT_LAZY_TYPE = supportSymbol
24 | ? Symbol.for('react.lazy')
25 | : 0xead4;
26 |
27 | export const REACT_MEMO_TYPE = supportSymbol
28 | ? Symbol.for('react.memo')
29 | : 0xead3;
30 |
--------------------------------------------------------------------------------
/packages/shared/ReactTypes.ts:
--------------------------------------------------------------------------------
1 | export type Type = any;
2 | export type Key = any;
3 | export type Ref = { current: any } | ((instance: any) => void);
4 | export type Props = any;
5 | export type ElementType = any;
6 |
7 | export interface ReactElementType {
8 | $$typeof: symbol | number;
9 | type: ElementType;
10 | key: Key;
11 | props: Props;
12 | ref: Ref;
13 | __mark: string;
14 | }
15 |
16 | export type Action = State | ((prevState: State) => State);
17 |
18 | export type ReactContext = {
19 | $$typeof: symbol | number;
20 | Provider: ReactProviderType | null;
21 | _currentValue: T;
22 | };
23 |
24 | export type ReactProviderType = {
25 | $$typeof: symbol | number;
26 | _context: ReactContext | null;
27 | };
28 |
29 | export type Usable = Thenable | ReactContext;
30 |
31 | export interface Wakeable {
32 | then(
33 | onFulfill: () => Result,
34 | onReject: () => Result
35 | ): void | Wakeable;
36 | }
37 |
38 | interface ThenableImpl {
39 | then(
40 | onFulfill: (value: T) => Result,
41 | onReject: (error: Err) => Result
42 | ): void | Wakeable;
43 | }
44 |
45 | interface UntrackedThenable
46 | extends ThenableImpl {
47 | status?: void;
48 | }
49 |
50 | export interface PendingThenable
51 | extends ThenableImpl {
52 | status: 'pending';
53 | }
54 |
55 | export interface FulfilledThenable
56 | extends ThenableImpl {
57 | status: 'fulfilled';
58 | value: T;
59 | }
60 |
61 | export interface RejectedThenable
62 | extends ThenableImpl {
63 | status: 'rejected';
64 | reason: Err;
65 | }
66 |
67 | export type Thenable =
68 | | UntrackedThenable
69 | | PendingThenable
70 | | FulfilledThenable
71 | | RejectedThenable;
72 |
--------------------------------------------------------------------------------
/packages/shared/internals.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const internals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
4 |
5 | export default internals;
6 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shared",
3 | "version": "1.0.0",
4 | "description": "所有公用辅助方法及类型定义",
5 | "keywords": [],
6 | "author": "",
7 | "license": "ISC"
8 | }
9 |
--------------------------------------------------------------------------------
/packages/shared/shallowEquals.ts:
--------------------------------------------------------------------------------
1 | export function shallowEqual(a: any, b: any): boolean {
2 | if (Object.is(a, b)) {
3 | return true;
4 | }
5 |
6 | if (
7 | typeof a !== 'object' ||
8 | a === null ||
9 | typeof b !== 'object' ||
10 | b === null
11 | ) {
12 | return false;
13 | }
14 |
15 | const keysA = Object.keys(a);
16 | const keysB = Object.keys(b);
17 |
18 | if (keysA.length !== keysB.length) {
19 | return false;
20 | }
21 |
22 | for (let i = 0; i < keysA.length; i++) {
23 | const key = keysA[i];
24 | // b没有key、 key不想等
25 | if (!{}.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) {
26 | return false;
27 | }
28 | }
29 | return true;
30 | }
31 |
--------------------------------------------------------------------------------
/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 | modulePathIgnorePatterns: ['/.history'],
7 | moduleDirectories: [...defaults.moduleDirectories, 'dist/node_modules'],
8 | testEnvironment: 'jsdom',
9 | moduleNameMapper: {
10 | '^scheduler$': '/node_modules/scheduler/unstable_mock.js'
11 | },
12 | fakeTimers: {
13 | enableGlobally: true,
14 | legacyFakeTimers: true
15 | },
16 | setupFilesAfterEnv: ['./scripts/jest/setupJest.js']
17 | };
18 |
--------------------------------------------------------------------------------
/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 | };
47 |
--------------------------------------------------------------------------------
/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 | };
94 |
--------------------------------------------------------------------------------
/scripts/jest/setupJest.js:
--------------------------------------------------------------------------------
1 | expect.extend({
2 | ...require('./reactTestMatchers')
3 | });
4 |
--------------------------------------------------------------------------------
/scripts/rollup/dev.config.js:
--------------------------------------------------------------------------------
1 | import reactDomConfig from './react-dom.config';
2 | import reactNoopRendererConfig from './react-noop-renderer.config';
3 | import reactConfig from './react.config';
4 |
5 | export default () => {
6 | return [...reactConfig, ...reactDomConfig, ...reactNoopRendererConfig];
7 | };
8 |
--------------------------------------------------------------------------------
/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 | ];
65 |
--------------------------------------------------------------------------------
/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(
6 | 'react-noop-renderer'
7 | );
8 | // react-dom包的路径
9 | const pkgPath = resolvePkgPath(name);
10 | // react-dom产物路径
11 | const pkgDistPath = resolvePkgPath(name, true);
12 |
13 | export default [
14 | // react-noop-renderer
15 | {
16 | input: `${pkgPath}/${module}`,
17 | output: [
18 | {
19 | file: `${pkgDistPath}/index.js`,
20 | name: 'ReactNoopRenderer',
21 | format: 'umd'
22 | }
23 | ],
24 | external: [...Object.keys(peerDependencies), 'scheduler'],
25 | plugins: [
26 | ...getBaseRollupPlugins({
27 | typescript: {
28 | exclude: ['./packages/react-dom/**/*'],
29 | tsconfigOverride: {
30 | compilerOptions: {
31 | paths: {
32 | hostConfig: [`./${name}/src/hostConfig.ts`]
33 | }
34 | }
35 | }
36 | }
37 | }),
38 | // webpack resolve alias
39 | alias({
40 | entries: {
41 | hostConfig: `${pkgPath}/src/hostConfig.ts`
42 | }
43 | }),
44 | generatePackageJson({
45 | inputFolder: pkgPath,
46 | outputFolder: pkgDistPath,
47 | baseContents: ({ name, description, version }) => ({
48 | name,
49 | description,
50 | version,
51 | peerDependencies: {
52 | react: version
53 | },
54 | main: 'index.js'
55 | })
56 | })
57 | ]
58 | }
59 | ];
60 |
--------------------------------------------------------------------------------
/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 |
4 | import ts from 'rollup-plugin-typescript2';
5 | import cjs from '@rollup/plugin-commonjs';
6 | import replace from '@rollup/plugin-replace';
7 |
8 | const pkgPath = path.resolve(__dirname, '../../packages');
9 | const distPath = path.resolve(__dirname, '../../dist/node_modules');
10 |
11 | export function resolvePkgPath(pkgName, isDist) {
12 | if (isDist) {
13 | return `${distPath}/${pkgName}`;
14 | }
15 | return `${pkgPath}/${pkgName}`;
16 | }
17 |
18 | export function getPackageJSON(pkgName) {
19 | // ...包路径
20 | const path = `${resolvePkgPath(pkgName)}/package.json`;
21 | const str = fs.readFileSync(path, { encoding: 'utf-8' });
22 | return JSON.parse(str);
23 | }
24 |
25 | export function getBaseRollupPlugins({
26 | alias = {
27 | __DEV__: true,
28 | preventAssignment: true
29 | },
30 | typescript = {}
31 | } = {}) {
32 | return [replace(alias), cjs(), ts(typescript)];
33 | }
34 |
--------------------------------------------------------------------------------
/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: [
10 | react(),
11 | replace({
12 | __DEV__: true,
13 | preventAssignment: true
14 | })
15 | ],
16 | resolve: {
17 | alias: [
18 | {
19 | find: 'react',
20 | replacement: resolvePkgPath('react')
21 | },
22 | {
23 | find: 'react-dom',
24 | replacement: resolvePkgPath('react-dom')
25 | },
26 | {
27 | find: 'react-noop-renderer',
28 | replacement: resolvePkgPath('react-noop-renderer')
29 | },
30 | {
31 | find: 'hostConfig',
32 | replacement: path.resolve(
33 | resolvePkgPath('react-dom'),
34 | './src/hostConfig.ts'
35 | )
36 | }
37 | ]
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------