实现一个React
环境搭建
- commit 校验 husky
- 代码格式校验 eslint
- 打包工具 rollup
package.json
- main 对应的commonjs 规范的入口
- module 对应的是 esmodule 规范的入口
fiberNode
介于 ReactElement 和 DOM 之间;用于reconciler,对于同一个节点,比较其ReactElement和fiberNode, 生成子fiberNode,并根据比较的结果生成不同标记(插入、删除、移动)
workLoop
完成fiber 树的构建,并且标记好对于fiber的操作
commit 阶段
- beforeMutation 阶段
- mutation 阶段
- layout 阶段
dependencies 与 peerDependencies 的区别
Placement 过程简介
- createRoot
创建 fiberRootNode - render 开始渲染,传入要渲染的ReactElement
- createUpdate
创建一个update,将其放入,rootFiber 的updateQueue中 - scheduleUpdateOnFiber
- markUpdateFromFiberToRoot
找到fiberRootNode - preparereFreshStack
创建 根workInProgress - workLoop
创建fiber树,并完成DOM树的生成 - performUnitOfWork
进入递归阶段 完成fiber树和DOM树的生成 - beginWork
从根节点开始,从updateQueue中拿到对应的ReactElement - reconcileChildren
将ReactElement 转换成 fiber - completeUnitOfWork
从最里面的叶子fiber开始,创建DOM。并将子DOM进行挂载在当前的真实DOM上。 - commitRoot
从fiberRootNode 开始。 - commitMutationEffects
传入finishedWork,将被标记的fiber,进行对应的DOM操作
FunctionComponent 的实现
需要考虑的问题
- 如何支持FC
- 如何组织hooks
如何支持FC
FC基于:
- beginWork
- completeWork
关于useState
hook 脱离FC上下文,仅仅是普通函数,如何让他拥有感知上下文的能力? 比如:
- hook如何知道在另一个hook的上下文环境
- hook 怎么知道当前是mount还是update 解决方啊:在不同上下文调用的hook不是同一个函数
- hooks如何知道自身的数据保存在哪? 可以记录当前正在render 的FC对应的fiberNode,在fiberNode 中保存数据;
关于函数组件的初步理解
函数组件的关键是形成ReactElement,在这个形成的过程中,设计了一套链表解构的hooks. 同时实现了使用hooks方法来触发组建的更新。同时通过全局变量的方式来确定当前fiber和当前hooks指针。
实现测试环节
update 初探
对于beginWork
- 处理 ChildDeletion 的情况
- 处理节点移动的情况
对于completeWork
- 需要处理HostText 内容更新的情况
- 需要处理HostComponent 属性变化的情况
对于commitWork
- 对于childDeletion,需要遍历被删除的子树
对于useState
- 实现相对于 mountState的updateState
commitWork流程
对于标记 ChildDeletion 子树;由于子树中:
- 对于FC,需要处理 useEffect,unmount执行,解绑ref
- 对于HostComponent ,需要解绑 ref
- 对于子树 的 根HostComponent,需要移除DOM
实现事件系统
- 模拟实现浏览器事件捕获、冒泡流程
- 实现合成事件对象
- 方便后续扩展
实现ReactDOM 与 Reconciler 对接
将事件保存在DOM中,通过创建以下两个对接时机
- 创建DOM时
- 更新属性时
触发事件系统
- 由于之前事件已经将props存储在了DOM中
- 获取到绑定事件的元素。
- 从该元素开始,向上收集 capture 和 bubble 事件
- 按顺序 触发收集到的事件数组
diff 过程
- 将之前的 节点利用key值或者index,存在一个map中
- 遍历新的 fiber 节点。根据新的节点的key 值,从之前的map数据中获取该节点。
- 如果之前存在该节点,那就复用该节点;不存在之前的就重新创建新的fiber 节点
- 比较index,标记该fiber 节点是移动还是插入
第十三章
第13章节出现了一个deleteRemainingChildren.但是在之前的章节中并没有出现过这个函数的实现。有点断层的感觉。 所以不打算实现该章节的代码了。
第十四章
批处理的实现。例如,多次调用setState的时候,只用触发依次render
- react是在微任务中进行的渲染
lane 模型
用于批处理更新调度。
不同的effect
-
useEffect
依赖变化后的,当前的commit阶段完成后异步执行 -
useLayoutEffect
在当前commit阶段同步执行 -
useInsertionEffect
在当前commit阶段同步执行,无法拿到dom的引用,主要是给css ,js库使用
实现 useEffect 需要的功能
- 保存依赖
- 需要能够保存 create 回调
- 需要能够保存 destroy 回调
- 需要能够区分是否触发 create 回调
- mount 时
- 以来变化时