{"id":16498331,"url":"https://github.com/geekskai/react18","last_synced_at":"2025-09-11T13:40:02.192Z","repository":{"id":182719796,"uuid":"668964488","full_name":"geekskai/react18","owner":"geekskai","description":"从0到1实现react18的功能","archived":false,"fork":false,"pushed_at":"2023-07-26T01:15:34.000Z","size":113,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-05T06:26:11.769Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/geekskai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-21T03:00:26.000Z","updated_at":"2025-02-13T08:37:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"7aa90218-f2d4-4365-a841-d2e3f26fef99","html_url":"https://github.com/geekskai/react18","commit_stats":null,"previous_names":["geekskai/react18"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/geekskai/react18","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Freact18","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Freact18/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Freact18/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Freact18/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/geekskai","download_url":"https://codeload.github.com/geekskai/react18/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Freact18/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274647494,"owners_count":25324292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-11T02:00:13.660Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T14:48:08.075Z","updated_at":"2025-09-11T13:40:02.120Z","avatar_url":"https://github.com/geekskai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react18\n\n从0到1实现react18的功能\n\n## 初始化搭建整个项目的工程\n\n### 在分支 release/init-project\n\n1. 定义项目结构（monorepo）\n2. 定义开发规范（lint、commit、tsc、代码风格）\n3. 选择打包工具（rollup）\n\n### 在分支 release/implement-JSX 实现JSX\n\n1. 实现ReactElement 构造函数\n\n- 新建 packages/shared/ReactSymbols.ts 用于存放独一无二的类型\n\n2. 实现jsx方法和使用rollup打包\n\n3. 实现 Reconciler(协调器)\n\n- 首先为什么react 需要使用到 Reconciler ?\n\n- 什么是jsx?\n  jsx 是由开发者编写的代码，经Babel编译成jsx方法的执行，执行的方法的返回值就是一个react element。\n  介于react element和DOM element之间的一种数据结构那就是FiberNode\n\n- 什么是FiberNode（虚拟DOM在react中的实现）\n  因为单独的react element无法表现出节点与节点相互之间的关系，无法表达当前组件的状态，所以需要一种新的数据结构，这种数据结构介于React Element与真实dom之间。而且还能表达出节点于节点之间的关系，也要能方便拓展，不仅能做完数据存储单元，还能做完工作单元。\n\n```\nclass FiberNode {\n\ttype: any;\n\ttag: WorkTag;\n\tpendingProps: Props;\n\tkey: Key;\n\tstateNode: any;\n\tref: Ref;\n\n\treturn: FiberNode | null;\n\tsibling: FiberNode | null;\n\tchild: FiberNode | null;\n\tindex: number;\n\n\tmemoizedProps: Props | null;\n\talternate: FiberNode | null;\n\tflags: Flags;\n\n\tconstructor(tag: WorkTag, pendingProps: Props, key: Key) {\n\t\t// 实例\n\t\tthis.tag = tag;\n\t\tthis.key = key;\n\t\t// HostComponent \u003cdiv\u003e div DOM\n\t\tthis.stateNode = null;\n\t\t// FunctionComponent 是具体的函数 () =\u003e {}\n\t\tthis.type = null;\n\n\t\t// 构成树状结构\n\t\tthis.return = null;\n\t\tthis.sibling = null;\n\t\tthis.child = null;\n\t\tthis.index = 0;\n\n\t\tthis.ref = null;\n\n\t\t// 作为工作单元\n\t\tthis.pendingProps = pendingProps;\n\t\tthis.memoizedProps = null;\n\n\t\tthis.alternate = null;\n\t\t// 副作用\n\t\tthis.flags = NoFlags;\n\t}\n}\n\n```\n\n#### reconciler的工作方式\n\n- 对于同一个节点，比较其React Element 与FiberNode，同时生成子FiberNode，根据比较的结果，生成不同的标记（比如说：插入，移动，删除等）\n\n1. 比如说需要在页面上挂载 `\u003cdiv\u003efoo\u003c/div\u003e`，这段代码经过jsx(\"div\")转换后，生成React Element，然后将React Element与当前的FiberNode进行比较，由于是第一次挂载，当前的FiberNode为null，所以比较的结果是生成新的子FiberNode,然后给子FiberNode打上Placement的标记\n\n2. 如果说将`\u003cdiv\u003efoo\u003c/div\u003e` 更新为`\u003cp\u003efoo\u003c/p\u003e`,那么同样这段代码经过jsx(\"p\")转换后，生成React Element,然后拿这个React Element和p对应的 FiberNode(那么此时这个FiberNode就是一个type为div的FiberNode)进行比较，然后生成子fibreNode，将div对应的fiberNode 标记为Deletion，将p对应的fiberNode标记为Placement\n\n3. 当所有ReactElement比较完后，会生成一棵fiberNode树，一共会存在两棵fiberNode树：\n\n- current：与视图中真实UI对应的fiberNode树\n- workInProgress：触发更新后，正在reconciler中计算的fiberNode树\n\n### jsx的执行顺序\n\n以DFS（深度优先遍历）的顺序遍历ReactElement，这意味着：\n\n如果有子节点，遍历子节点\n如果没有子节点，遍历兄弟节点\n\n这是个递归的过程，存在递、归两个阶段：\n\n递：对应beginWork\n归：对应completeWork\n\n## 从入口开始分析\n\n假如有以下代码：\n\n```\nconst root = document.getElementById(\"root\");\n\nconst App = ()=\u003e{\n\n    return \u003cdiv\u003ehello world\u003c/div\u003e\n}\n\nReactDOM.createRoot(root).render(\u003cApp /\u003e);\n\n```\n\n对于在react-dom中createRoot（简化版本）如下：\n\n```\nexport function createRoot(container: Container) {\n\tconst root = createContainer(container);\n\n\treturn {\n\t\trender(element: ReactElementType) {\n\t\t\tinitEvent(container, 'click');\n\t\t\treturn updateContainer(element, root);\n\t\t}\n\t};\n}\n```\n\n先将createRoot 分开为以下2步分别执行：\n\n第一步：ReactDOM.createRoot(root) 对应执行的是`createContainer(container);`返回`root`,然后看下createContainer的具体实现（简化版本）如下：\n\n```\n// container就是#root对应的真实DOM Element\nexport function createContainer(container: Container) {\n\t/**\n\t * 创建一个FiberNode，对应的tag类型是HostRoot\n\t * 如果是一个普通的`\u003cdiv\u003e...\u003c/div\u003e`，在创建其对应的FiberNode的时候，那么对应的tag类型是 HostComponent\n\t * 如果是一个函数组件`\u003cApp/\u003e`，在创建其对应的FiberNode的时候，那么对应的tag类型是FunctionComponent\n\t *\n\t * 所以：对于hostRootFiber这个fiber来说有2种含义，\n\t * 第一种是：其对应的真实DOM是一个div,\n\t * 第二种是：这个div不是普通的div,而且还是整个应用挂载的根节点\n\t */\n\tconst hostRootFiber = new FiberNode(HostRoot, {}, null);\n\n\t/*\n\t * 在FiberRootNode中，将真实DOM和对应的fiber节点进行对应关联，形成关联关系\n\t *  1. 将FiberRootNode.current 指向了 hostRootFiber\n\t *  2. 将hostRootFiber的真实DOM指向了container （hostRootFiber.stateNode = FiberRootNode）\n\t */\n\tconst root = new FiberRootNode(container, hostRootFiber);\n\n\t/**\n\t * FiberNode和 FiberRootNode这两者之间有什么不同呢？\n\t * - 首先FiberRootNode对应的fiber节点不是普通的fiber节点，而是整个应用的根fiber节点，所以需要和普通的fiber区别对待\n\t * 这个FiberRootNode对应的组件是我们需要挂载的整个应用的根root(也就是：document.getElementById(\"root\"))\n\t */\n\n\thostRootFiber.updateQueue = createUpdateQueue();\n\n\treturn root;\n}\n\nexport const createUpdateQueue = \u003cState\u003e() =\u003e {\n\treturn {\n\t\tshared: {\n\t\t\tpending: null\n\t\t},\n    // 省略其他代码...\n\t} as UpdateQueue\u003cState\u003e;\n};\n\n```\n\n第二步：`ReactDOM.createRoot(root).render(\u003cApp /\u003e);`中的 `render(\u003cApp /\u003e);`\n\n```\nrender(element: ReactElementType) {\n    // 省略其他代码...\n\treturn updateContainer(element, root);\n}\n\nexport function updateContainer(element: ReactElementType | null, root: FiberRootNode) {\n    // 省略其他代码...\n\n    const hostRootFiber = root.current;\n\n    // 创建新的update对象\n    const update = createUpdate\u003cReactElementType | null\u003e(element);\n\n    // 将新的update对象并将其放入hostRootFiber的updateQueue中\n    enqueueUpdate(\n        hostRootFiber.updateQueue as UpdateQueue\u003cReactElementType | null\u003e,\n        update\n    );\n\n    // 开启调度功能\n    scheduleUpdateOnFiber(hostRootFiber);\n\n    // 省略其他代码...\n\n   return element;\n}\n\n```\n\n现在看下 `scheduleUpdateOnFiber` 的实现\n\n```\n// 简化版的调度功能\nexport function scheduleUpdateOnFiber(fiber: FiberNode) {\n    /**\n     * 为啥要先获取root呢？\n     * 因为任意一次更新，都是需要从hostRootFiber开始调度的，但是更新不一定只发生在root组件中，也有可能发生在其他任意组件中，\n     * 所以只要发生更新，就需要从当前组件的fiber开始向上查找，直到找到 hostRootFiber\n     */\n\tconst root = markUpdateFromFiberToRoot(fiber);\n\n    // 此时从root开始渲染，请看renderRoot的具体实现\n\trenderRoot(root);\n}\n\n//  从传入的fiber开始，向上查找，直到找到FiberRootNode为止\nfunction markUpdateFromFiberToRoot(fiber: FiberNode) {\n\tlet node = fiber;\n\tlet parent = node.return;\n\n   // 如果一个fiber.return存在，那么这个fiber肯定不是hostRootFiber，因为hostRootFiber没有return,只有stateNode\n \twhile (parent !== null) {\n\t\tnode = parent;\n\t\tparent = node.return;\n\t}\n\n   // 如果一直向上找直到parent为null，那么判断当前fiber的tag是否是HostRoot，\n   // 如果是就直接返回stateNode，因为hostRootFiber.stateNode 就是hostRootFiber\n\tif (node.tag === HostRoot) {\n\t\treturn node.stateNode;\n\t}\n\n\treturn null;\n}\n\nlet workInProgress: FiberNode | null = null;\n\nfunction prepareFreshStack(root: FiberRootNode) {\n\t// 创建hostRootFiber的workInProgress树\n\tworkInProgress = createWorkInProgress(root.current, {});\n}\n\nfunction renderRoot(root: FiberRootNode) {\n\t// 初始化\n\tprepareFreshStack(root);\n\n\tdo {\n\t\ttry {\n\t\t\tworkLoop();\n\t\t\tbreak;\n\t\t} catch (e) {\n\t\t\tconsole.warn('workLoop发生错误', e);\n\t\t\tworkInProgress = null;\n\t\t}\n\t} while (true);\n}\n\n```\n\n在renderRoot中，初始化的时候会在调用prepareFreshStack，创建一个hostRootFiber类型的workInProgress树，之后进入执行workLoop函数阶段\n\n```\n\nfunction workLoop() {\n\t// 第一个workInProgress肯定是初始化的时候hostRootFiber类型的workInProgress\n\twhile (workInProgress !== null) {\n\t\tperformUnitOfWork(workInProgress);\n\t}\n}\n\nfunction performUnitOfWork(fiber: FiberNode) {\n\t// 开始向下递阶段的执行beginWork\n\tconst next = beginWork(fiber);\n\tfiber.memoizedProps = fiber.pendingProps;\n\n\t// 如果递阶段完成了，那么就是归阶段开始\n\tif (next === null) {\n\t\tcompleteUnitOfWork(fiber);\n\t} else {\n\t\tworkInProgress = next;\n\t}\n}\n```\n\nbeginWork 的工作流程:\n如果是HostRoot类型的，那么就需要计算状态的最新值，然后创造子fiberNode\n\n```\nexport const beginWork = (wip: FiberNode) =\u003e {\n\t// 比较，创建并返回子fiberNode\n\tswitch (wip.tag) {\n\t\tcase HostRoot:\n\t\t\t// 目的是计算状态的最新值，然后创造并返回子fiberNode\n\t\t\treturn updateHostRoot(wip);\n\t\tcase HostComponent:\n\t\t\treturn updateHostComponent(wip);\n\t\tcase HostText:\n\t\t\treturn null;\n\t\tdefault:\n\t\t\tif (__DEV__) {\n\t\t\t\tconsole.warn('beginWork未实现的类型');\n\t\t\t}\n\t\t\tbreak;\n\t}\n\treturn null;\n};\n\nfunction updateHostRoot(wip: FiberNode) {\n\t// 取到上一次的值\n\tconst baseState = wip.memoizedState;\n\t// 从updateQueue中取出即将更新的数据\n\tconst updateQueue = wip.updateQueue as UpdateQueue\u003cElement\u003e;\n\t// pending就是即将需要更新的数据，先用一个变量保存起来\n\tconst pending = updateQueue.shared.pending;\n\t// 更新完了就直接重置为null,因为数据已经保存在pending中了。\n\tupdateQueue.shared.pending = null;\n\t// 进入计算更新值的阶段，memoizedState就是计算更新后的最终结果\n\tconst { memoizedState } = processUpdateQueue(baseState, pending);\n\t// 计算更新后的最终结果更新到wip中\n\twip.memoizedState = memoizedState;\n\n   // 对于 HostRoot 类型的fiber来说，memoizedState其实就是一个App类型的 ReactElement\n\tconst nextChildren = wip.memoizedState;\n\n\t// 在这个函数中创建子fiber，并将其更新到wip.child中,加下来看下这个函数的实现\n\treconcileChildren(wip, nextChildren);\n\n\t// 返回子fiber\n\treturn wip.child;\n}\n```\n\nprocessUpdateQueue就是执行具体更新方法的\n\n```\nexport const processUpdateQueue = \u003cState\u003e(\n\tbaseState: State,\n\tpendingUpdate: Update\u003cState\u003e | null\n): { memoizedState: State } =\u003e {\n\tconst result: ReturnType\u003ctypeof processUpdateQueue\u003cState\u003e\u003e = {\n\t\tmemoizedState: baseState\n\t};\n\tif (pendingUpdate !== null) {\n\t\t// action 可能是开发者传入的一个函数（this.setState(state=\u003estate+1)）或者一个具体值(比如说：this.setState(state))\n\t\tconst action = pendingUpdate.action;\n\t\t// action 如果是函数比如说：state=\u003estate+1\n\t\tif (action instanceof Function) {\n\t\t\t// baseState 1 update (x) =\u003e 4x -\u003e memoizedState 4\n\t\t\tresult.memoizedState = action(baseState);\n\t\t} else {\n\t\t\t// baseState 1 update 2 -\u003e memoizedState 2\n\t\t\tresult.memoizedState = action;\n\t\t}\n\t}\n\treturn result;\n};\n```\n\n通过对比，创建子fiber节点并将其更新到wip.child中\n\n```\nfunction reconcileChildren(wip: FiberNode, children?: ReactElementType) {\n\t// current 就是当前已经渲染在界面中的fiber树，\n\tconst current = wip.alternate;\n\n\t// current.child就是老数据，children就是新数据，需要将两者进行对比，从而产生新的子fiber\n\n\tif (current !== null) {\n\t\t// update\n\t\twip.child = reconcileChildFibers(wip, current.child, children);\n\t} else {\n\t\t// mount\n\t\twip.child = mountChildFibers(wip, null, children);\n\t}\n}\n```\n\n接下来就是重点ChildReconciler，有2点比较重要，第一点是通过子element创建子fiber的方式，第二点是给子fiber打上flag的标识，\n\n```\nfunction ChildReconciler(shouldTrackEffects: boolean) {\n\tfunction reconcileSingleElement(\n\t\treturnFiber: FiberNode,\n\t\tcurrentFiber: FiberNode | null,\n\t\telement: ReactElementType\n\t) {\n\t\t// 根据element创建fiber\n\t\tconst fiber = createFiberFromElement(element);\n\t\tfiber.return = returnFiber;\n\t\treturn fiber;\n\t}\n\tfunction reconcileSingleTextNode(\n\t\treturnFiber: FiberNode,\n\t\tcurrentFiber: FiberNode | null,\n\t\tcontent: string | number\n\t) {\n\t\tconst fiber = new FiberNode(HostText, { content }, null);\n\t\tfiber.return = returnFiber;\n\t\treturn fiber;\n\t}\n\n\tfunction placeSingleChild(fiber: FiberNode) {\n\t\tif (shouldTrackEffects \u0026\u0026 fiber.alternate === null) {\n\t\t\tfiber.flags |= Placement;\n\t\t}\n\t\treturn fiber;\n\t}\n\n\treturn function reconcileChildFibers(\n\t\treturnFiber: FiberNode,\n\t\tcurrentFiber: FiberNode | null,\n\t\tnewChild?: ReactElementType\n\t) {\n\t\t// 判断当前fiber的类型\n\t\tif (typeof newChild === 'object' \u0026\u0026 newChild !== null) {\n\t\t\tswitch (newChild.$$typeof) {\n\t\t\t\tcase REACT_ELEMENT_TYPE:\n\t\t\t\t\treturn placeSingleChild(\n\t\t\t\t\t\treconcileSingleElement(returnFiber, currentFiber, newChild)\n\t\t\t\t\t);\n\t\t\t\tdefault:\n\t\t\t\t\tif (__DEV__) {\n\t\t\t\t\t\tconsole.warn('未实现的reconcile类型', newChild);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t// TODO 多节点的情况 ul\u003e li*3\n\n\t\t// HostText\n\t\tif (typeof newChild === 'string' || typeof newChild === 'number') {\n\t\t\treturn placeSingleChild(\n\t\t\t\treconcileSingleTextNode(returnFiber, currentFiber, newChild)\n\t\t\t);\n\t\t}\n\n\t\tif (__DEV__) {\n\t\t\tconsole.warn('未实现的reconcile类型', newChild);\n\t\t}\n\t\treturn null;\n\t};\n}\n\nexport const reconcileChildFibers = ChildReconciler(true);\nexport const mountChildFibers = ChildReconciler(false);\n\n\n```\n\n执行完了所有beginWork之后，就进入了completeWork阶段，completeWork 需要解决的其中一个问题就是：对Host类型的FiberNode构建离屏DOM树\n\n```\nfunction completeUnitOfWork(fiber: FiberNode) {\nlet node: FiberNode | null = fiber;\n\n    do {\n    \tcompleteWork(node);\n\n    \tconst sibling = node.sibling;\n\n    \tif (sibling !== null) {\n    \t\tworkInProgress = sibling;\n\n    \t\treturn;\n    \t}\n    \tnode = node.return;\n    \tworkInProgress = node;\n    } while (node !== null);\n\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekskai%2Freact18","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeekskai%2Freact18","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekskai%2Freact18/lists"}