React底层架构
目录
前言
本文是React源码学习系列第一篇,该系列整体都基于React18.0.0版本源码。旨在学习过程中记录一些个人的理解。该篇整体介绍React的底层架构及数据结构。为后面的阅读打下坚实基础。
React理念
自React16.8版本重构后,React大致可以分为三个模块。
Scheduler
Scheduler(调度器):负责调度任务的优先级,高优先级的任务优先进入Reconciler。
Reconciler
Reconciler(协调器):负责Diff前后对比,计算出最小DOM变化,生成一棵Fiber树。
Renderer
Renderer(渲染器):负责将DOM变化渲染到宿主环境中。
Fiber架构
React代码中可以人为的分为三种节点。
React Component
我们日常开发中编写的组件。
React Element
即VDOM,jsx方法返回的值。它是UI与Fiber节点的桥梁。 我们编写的代码会在编译阶段调用_jsxRuntime.jsx方法返回ReactElement实例。
FiberNode
Diff阶段通过ReactElement生成,组成Fiber树的最小节点。用来描述DOM。 三者的关系如下
// App 是 React组件
const App = () => {
return <h1>Hello world.<h1/>
}
// ele 是 React Element,编译时会调用jsx生成
// _jsxRuntime.jsx(type, config);
const ele = <App />
// React运行时会在内部创建App对应的FiberNode
ReactDOM.createRoot(root).render(ele);
FiberNode构造函数
// 保留关键代码
function FiberNode(
tag: WorkTag
) {
this.tag = tag;
this.key = key;
// 基本跟type类似
this.elementType = null;
// Fiber类型DOM元素为实际dom标签名称,FC组件为函数本身
this.type = null;
// 对应DOM元素,HostRootFiber为FiberRootNode
this.stateNode = null;
// 链表相关
this.return = null; // 指向父Fiber
this.child = null; // 指向第一个子Fiber
this.sibling = null; // 指向兄弟Fiber
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps; // 新的props
this.memoizedProps = null; // 老的props
this.updateQueue = null; // 更新队列
this.memoizedState = null; // 老state
// Fiber自身的副作用
this.flags = NoFlags;
// 记录子Fiber树包含的副作用
this.subtreeFlags = NoFlags;
// 待删除的子Fiber
this.deletions = null;
// Fiber自身待更新lanes
this.lanes = NoLanes;
// 记录子Fiber树的待更新lanes
this.childLanes = NoLanes;
// 双缓存机制相关
this.alternate = null;
}
memoizedState FC组件保存的是第一个hook。
Fiber树示例
双缓存机制
Fiber架构中共有两课Fiber Tree。一棵是current Fiber Tree,真实渲染的Fiber Tree。另一棵是workInProgress Fiber Tree,正在内存中构建的Fiber Tree。他们通过alternate属性互相指向。
current.alternate = workInProgress;
workInProgress.alternate = current;
React会在首次渲染时创建FiberRootNode,创建tag为3的FiberNode(HostRootFiber),然后从HostRootFiber开始以深度优先的顺序生成Fiber Tree。
FiberRootNode为整个应用的根,唯一。负责管理应用的全局事宜。 FiberRootNode.curret指向current Fiber Tree。在commit阶段对current Fiber Tree 和 workInProgess Fiber Tree进行切换。
HostRootFiber为Fiber Tree的根,不唯一。
FiberRootNode构造函数
// 保留关键代码
function FiberRootNode(
containerInfo,
tag,
) {
// 并发模式为1、历史模式为0
this.tag = tag;
// 挂载应用的DOM节点,<div id="root"></div>
this.containerInfo = containerInfo;
// 指向current Fiber Tree
this.current = null;
// 当前完成的Fiber节点。通常是完成Render阶段的wip的根节点HostRootFiber节点。
this.finishedWork = null;
// 本次更新执行的任务
this.callbackNode = null;
// 本次更新的优先级
this.callbackPriority = NoLane;
// 长度为31的array,初始都是NoLanes
this.eventTimes = createLaneMap(NoLanes);
// 长度为31的array,初始都是-1
this.expirationTimes = createLaneMap(NoTimestamp);
// 所有待更新任务对应lane的集合,31位2进制格式
this.pendingLanes = NoLanes;
// 挂起的更新任务对应lane的集合
this.suspendedLanes = NoLanes;
// 从挂起状态恢复的更新任务对应lane的集合
this.pingedLanes = NoLanes;
// 所有过期更新lane集合
this.expiredLanes = NoLanes;
// 本次更新已完成的lane集合
this.finishedLanes = NoLanes;
// 跟lane纠缠相关
this.entangledLanes = NoLanes;
this.entanglements = createLaneMap(NoLanes);
}
双缓存示例
React中的位元算
按位与
0b000 0000 0000 0000 0000 0000 0000 0001
&
0b000 0000 0000 0000 0000 0000 0000 0011
=
0b000 0000 0000 0000 0000 0000 0000 0001
按位或
0b000 0000 0000 0000 0000 0000 0000 0001
|
0b000 0000 0000 0000 0000 0000 0000 0011
=
0b000 0000 0000 0000 0000 0000 0000 0011
按位非
0b000 0000 0000 0000 0000 0000 0000 0001
~
0b111 1111 1111 1111 1111 1111 1111 1110
React源码中大量使用了位运算。
示例
// 打上Placement标记
fiber.flags |= Placement;
// 移除Placement标记
fiber.flags &= ~Placement;
// 检查是否有Placement标记
fiber.flags & Placement === Placement;
lane模型
31位2进制数据,可以表示31种优先级。
React优先级
React有4种优先级。
const SyncLane = /* */ 0b0000000000000000000000000000001;
const InputContinuousLane = 0b0000000000000000000000000000100;
const DefaultLane = /* */ 0b0000000000000000000000000010000;
const IdleLane = /* */ 0b0100000000000000000000000000000;
const DiscreteEventPriority = SyncLane;
const ContinuousEventPriority = InputContinuousLane;
const DefaultEventPriority = DefaultLane;
const IdleEventPriority = IdleLane;
Scheduler优先级
Scheduler有5种优先级
const ImmediatePriority = 1;
const UserBlockingPriority = 2;
const NormalPriority = 3;
const LowPriority = 4;
const IdlePriority = 5;
过期时间
创建任务的时候会在任务的开始时间加上优先级对应的过期时间,计算出该任务的过期时间。
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// 0b111111111111111111111111111111
var IDLE_PRIORITY_TIMEOUT = 1073741823;
Update
React中可以触发更新的方法。
- root.render() --- HostRootFiber
- this.setState() --- ClassComponent
- this.forceUpdate() --- ClassComponen
- useState dispatcher --- FunctionComponen
- useReducer dispatcher --- FunctionComponen
数据结构
upadte是计算state的最小单位。
ClassComponent与HostRootFiber Update结构:
const update = {
eventTime,
lane,
// 区分更新触发的场景
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
- const UpdateState = 0; root.render或者this.setState
- const ReplaceState = 1; ClassComponent生命周期中直接改变this.state
- const ForceUpdate = 2; this.forceUpdate触发
- const CaptureUpdate = 3; 发生错误时触发的更新
FunctionComponen Update结构:
const update = {
lane,
action,
// 优化策略相关
hasEagerState: false,
eagerState: null,
next: null,
};
UpdateQueue
数据结构
const updateQueue = {
baseState: null,
firstBaseUpdate: null,
lastBaseUpdate: false,
shared: {
pending: null
},
};
- baseState是参与计算的初始state。
- firstBaseUpdate、lastBaseUpdate是本次更新前遗留的Upate,部分Update因为优先级低,在上次render阶段没有参与state的计算。会以链表形式保存在以下两个字段。firstBaseUpdate指向链表头,lastBaseUpdate指向链表尾。
- 触发更新后,产生的Update会保存在shared.pending。shared.pending指向第一个Update。shared.pending.next指向最后一个Update。
- 开始计算state前会把两个链表连接成新的链表。
示例
Hooks
数据结构
hook
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: queue,
// 指向下一个hook
next: null,
};
queue
const queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
- memoizedState根据不同hook保存的对应的数据。
- useState -> state
- useReducer -> state
- useEffect -> callback, [...deps]
- useRef -> {current: initialValue}
- useMemo -> [callback(), [...deps]]
- useCallback -> [callback, [...deps]]
- baseState是参与计算的初始state,如果没有遗留的Upate,那么等于memoizedState,有遗留的Upate会保存中间状态。
- baseQueue是本次更新前遗留的Upate,部分Update因为优先级低,在上次render阶段没有参与state的计算。会以链表形式保存。
- 触发更新后,产生的Update会保存在queue.pending。queue.pending指向第一个Update。queue.pending.next指向最后一个Update。
- 开始计算state前会把两个链表连接成新的链表。
示例
Effect
数据结构
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
- tag有三个类型
- const Insertion = 0b0010; -> useInsertionEffect
- const Layout = 0b0100; -> useLayoutEffect
- const Passive = 0b1000; -> useEffect
- create 回调函数
- destroy 组件销毁时执行函数
- deps 依赖项
- next字段指向当前Fiber的其他Effect,形成环状列表。
// 这里是tag
useEffect(() => {
// 这里是create
return () => {
// 这里是destroy
}
},[]) // 这里是deps
示例
参考
React设计原理 - 卡颂。
转载自:https://juejin.cn/post/7395866692771627048