├── .commitlintrc.js ├── .eslintrc.json ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierrc.json ├── README.md ├── babel.config.js ├── demos └── test-fc │ ├── index.html │ └── main.tsx ├── package.json ├── packages ├── react-dom │ ├── client.ts │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── SyntheticEvent.ts │ │ ├── hostConfig.ts │ │ └── root.ts │ └── test-utils.ts ├── react-noop-renderer │ ├── index.ts │ ├── package.json │ └── src │ │ ├── hostConfig.ts │ │ └── root.ts ├── react-reconciler │ ├── package.json │ └── src │ │ ├── __tests__ │ │ └── ReactEffectOrdering-test.js │ │ ├── beginWork.ts │ │ ├── childFiber.ts │ │ ├── commitWork.ts │ │ ├── completeWork.ts │ │ ├── fiber.ts │ │ ├── fiberFlags.ts │ │ ├── fiberHooks.ts │ │ ├── fiberLanes.ts │ │ ├── fiberReconciler.ts │ │ ├── hookEffectTags.ts │ │ ├── reconciler.d.ts │ │ ├── syncTaskQueue.ts │ │ ├── updateQueue.ts │ │ ├── workLoop.ts │ │ └── workTags.ts ├── react │ ├── __tests__ │ │ └── ReactElement-test.js │ ├── index.ts │ ├── jsx-dev-runtime.ts │ ├── package.json │ └── src │ │ ├── currentDispatcher.ts │ │ └── jsx.ts └── shared │ ├── ReactSymbols.ts │ ├── ReactTypes.ts │ ├── internals.ts │ └── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── jest │ ├── jest.config.js │ ├── reactTestMatchers.js │ ├── schedulerTestMatchers.js │ └── setupJest.js ├── rollup │ ├── dev.config.js │ ├── react-dom.config.js │ ├── react-noop-renderer.config.js │ ├── react.config.js │ └── utils.js └── vite │ └── vite.config.js └── tsconfig.json /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "prettier", 12 | "plugin:prettier/recommended" 13 | ], 14 | "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "ecmaVersion": "latest", 17 | "sourceType": "module" 18 | }, 19 | "plugins": ["@typescript-eslint", "prettier"], 20 | "rules": { 21 | "prettier/prettier": "error", 22 | "no-case-declarations": "off", 23 | "no-constant-condition": "off", 24 | "@typescript-eslint/ban-ts-comment": "off", 25 | "@typescript-eslint/no-unused-vars": "off", 26 | "@typescript-eslint/no-var-requires": "off", 27 | "@typescript-eslint/no-explicit-any": "off", 28 | "no-unused-vars": "off" 29 | } 30 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint -e 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm lint 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": true, 5 | "singleQuote": true, 6 | "semi": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 |

《手写 React 源码》

5 |

深入理解 React 源码,手把手带你构建自己的 React 库。

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