├── .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
      {items}
    ; 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 | --------------------------------------------------------------------------------