目标: 在页面中呈现app(使用React的API)
效果如下:
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
ReactDoM.createRoot(document.getElementById( "root")).render(<App />)
- 动态创建虚拟dom (注意虚拟dom的形式,要符合jsx的规范)
- 创建渲染函数
- 重构API
- 使用jsx代替掉js写法
- 借助vite实现jsx的解析
vite解析jsx的时,会调用React.createElement函数。这也是为什么我们平时在项目中并没有使用React确需要引入的原因
import React from "./core/React.js";
function Test() {
return <div id="test">test</div>;
}
console.log(Test);
// ƒ Test() {
// return /* @__PURE__ */ React.createElement("div", { id: "test" }, "test");
// }
目标: 实现任务调度器&fiber架构
requestIdleCallback(callback[, options])
callback
是需要执行的任务,接收一个 IdleDeadline ==对象==作为参数。IdleDeadline 包含 2 个重要字段
- didTimeout,布尔值,表示任务是否超时
- timeRemaining() ,用于获取当前帧的剩余时间
options
是一个可选参数,目前只有一个值 timeout
,表示如果超过这个时间,任务还没有执行,则强制执行任务,不需要等待空闲时间。
解决思路: 把树结构转变成链表结构 (边转换为链表边渲染)
- child
- sibling
- parent 的 sibling
performWorkOfUnit
- 创建dom
- 添加dom
- 处理props
- 转换链表 设置好指针
- 返回下一个要执行的workUnit
在render时设置好初始的workUnit
nextWorkOfUnit = {
dom: container,
props: {
children: [el],
},
};
存在问题: 中途可能无空余时间, 用户会看到渲染一半的dom
解决思路: 计算结束后统一添加到屏幕里
关键点:
- 何时处理完链表 ?
nextWorkOfUnit===null
- 根结点 ? render中初始化的
nextWorkOfUnit
- fiber的type若为function需要调用一下,传入props
- 处理children的格式
- 递归寻找有dom的父节点
- 更改文本节点的判断,添加number类型
- 更改寻找叔叔节点的逻辑
在处理props时拦截"on"开头的prop,并添加对应事件
核心: 对比新旧两颗dom树
问题:
-
如何得到新的dom树
render函数构建dom树 ---> update函数
定义currentRoot, 在重置root前赋值
-
如何找到老的节点
新节点创建一个属性alternate指向老的节点
initChildren时判断是更新/创建, 添加一个新属性effectTag(update/placement)
更新是不会创建dom的,直接用老的dom
绑定alternate
多个子节点需要更新oldFiber = oldFiber.sibling
在commitWork中判断更新还是插入
-
如何 diff props
updateProps(dom,nextProps,prevProps);
- old有 new没有 删除 removeAttribute
- new有 old无 添加
- new有 old有 修改
- 删除之前的回调
核心: type不一致 删除旧的 创建新的
在reconcileChildren
中判断了fiber的type是否一致,得到需要删除的fiber
设置一个数组存储需要删除的fiber 在commitRoot
阶段删除
Function Component需要进行特殊处理 fiber要有dom, fiber.parent也要有dom
删除完后需将数组置空
新的比老的短,多出来的节点需要删掉
多出来的节点为oldFiber.sibling
在创建newFiber前做判断来筛除 false 的情况,同时在创建完newFiber后再去更新保存的prevChild
问题: 更新子组件时, 其它不相关的组件也会重新执行 造成了浪费
开始节点: 当前要更新的FC
结束节点: 处理兄弟节点时
使用闭包存储真正需要更新的Fiber
在setState中执行更新逻辑
为currentFiber添加stateHook属性, 用来保存上一次更新后state的值
当处理多个state时会出现混乱, 使用数组和索引去解决
updateFunctionComponent时重置数组和索引
将action存储在queue中, 下次执行时统一执行
提前判断action的值与当前state是否一样
useEffect的调用时机: React完成对DOM的渲染之后,并且浏览器完成绘制之前
调用时机:commitWork之后
保存Callback和deps
从根结点一次递归,执行节点上的effecthook
初始化时直接执行,更新时需要检测依赖项是否发生改变(通过判断alternate的deps和当前deps是否相同)
支持多个useEffect也是使用数组存储
deps为空时,cleanup不会执行
在执行Callback时为cleanup赋值
执行effct前遍历(遍历的是之前的effectHook)执行cleanup