react实现分析
本文最后更新于:2022年7月12日 下午
React
⭐️ 时间切片
- 主流显示器的刷新率是 60Hz,浏览器渲染一帧需要执行
执行js脚本
,Layout重排
,Paint重绘
- 如果 js 脚本的执行时间超过 16.6ms,就会造成浏览器这次刷新没有时间去绘制,造成卡顿
- React 在每一帧中预留了时间给 js 任务执行(
5ms
),并将大的 js 任务拆分到每一帧中
React 架构
旧架构
-
Reconciler协调器
查找组件的更新 -
Renderer渲染器
将更新内容渲染到页面上 - 交替执行
-
Reconciler
使用递归来更新组件,一旦组件嵌套过深其实也会超过 16.6ms 造成卡顿
新架构
-
Scheduler
当目前帧浏览器有多余时间可用时调度任务进入Reconciler
-
Reconciler
为组件打上删除,更新,插入的标记 - 当
Reconciler
完成全部工作后交给Renderer
更新 DOM - 组件的更新(打标记)会由于任务优先级或者浏览器剩余时间的变化随时中断
Fiber 架构
一套应用程序的状态可中断可恢复的更新机制
⭐️ 双缓存
- 在内存中构建更新的状态并直接替换
- Fiber 树有两颗,前屏幕上显示内容对应的 Fiber 树称为
current Fiber
树,正在内存中构建的 Fiber 树称为workInProgress Fiber
树
每一个 Fiber 节点包含:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag; // class/func component
this.key = key; // ⭐️key
this.elementType = null;
this.type = null;
this.stateNode = null; // DOM
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.effectTag = NoEffect; // effect标记
this.subtreeTag = NoSubtreeEffect;
this.deletions = null;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null; // 上一次更新的fiberNode
}
- 组件类型
-
⭐️key
-
DOM
节点 - 指向其他 Fiber 节点的指针
- 本次更新的相关信息
- 调度优先级信息
执行阶段
render 阶段
从
rootFiber
开始向下深度遍历,调用 fiber 节点的beginWork
方法,创建新的子 fiber 节点,一直到叶子节点某个 fiber 节点执行完
completeWork
后,会访问其兄弟节点让他兄弟执行beginWork
没有兄弟节则
return
到父节点一直 return 到
rootFiber
则render
结束
beginWork
⭐️[[diff算法]]
创建子 fiber 节点,可能复用上一次更新的子 fiber 节点,或者新建新 fiber 节点,并在上面打上标记
- ⚠️
renderWithHooks
completeWork
区分 mount/update,生成新的 DOM 节点,并处理props
,return
到rootFiber
时就已拥有了一颗完整的 DOM 树
commit 阶段
执行effectList
,用来保存 fiber 节点需要执行副作用的单向链表,执行相应的 DOM 操作。
commitBeforeMutationEffects
-
getSnapshotBeforeUpdate
-
flushPassiveEffects
commitMutationEffects
- 解绑
ref
- ⚠️ 同步执行
useLayoutEffect
的销毁函数 - 根据不同的标记执行DOM操作
-
componentWillUnmount
switch root.current
切换 current fiber 树
commitLayoutEffects
- 赋值新的
ref
- ⚠️ 同步执行
useLayoutEffect
的回调 - ⚠️ 异步执行
useEffect
的销毁和回调 -
componentDidMount
-
componentDidUpdate
- 执行 render 函数的回调
状态更新
ReactDOM.render
this.setState
this.forceUpdate
useState
useReducer
每次触发状态更新都会新建Update
对象
组件中多次触发的update也会形成一条链表
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState, // 之前的state
firstBaseUpdate: null,
lastBaseUpdate: null,
// 👆本次更新前的update链表
shared: {
pending: null,
},
effects: null,
};
更新的优先级
- 生命周期钩子(同步)
- 用户输入(同步)
- UI交互(高优先级)
- 其他
- 一次
render
中的部分低优先级update
可能被跳过/插队
react实现分析
http://yoursite.com/2022/05/10/[源码]react/