likes
comments
collection
share

React底层架构

作者站长头像
站长
· 阅读数 55

目录

前言

本文是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树示例

React底层架构

双缓存机制

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底层架构

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中可以触发更新的方法。

  1. root.render() --- HostRootFiber
  2. this.setState() --- ClassComponent
  3. this.forceUpdate() --- ClassComponen
  4. useState dispatcher --- FunctionComponen
  5. useReducer dispatcher --- FunctionComponen

数据结构

upadte是计算state的最小单位。

ClassComponent与HostRootFiber Update结构:

const update = {
   eventTime,
   lane,
   // 区分更新触发的场景
   tag: UpdateState,
   payload: null,
   callback: null,
   next: null,
};
  1. const UpdateState = 0; root.render或者this.setState
  2. const ReplaceState = 1; ClassComponent生命周期中直接改变this.state
  3. const ForceUpdate = 2; this.forceUpdate触发
  4. 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
  },
};
  1. baseState是参与计算的初始state。
  2. firstBaseUpdate、lastBaseUpdate是本次更新前遗留的Upate,部分Update因为优先级低,在上次render阶段没有参与state的计算。会以链表形式保存在以下两个字段。firstBaseUpdate指向链表头,lastBaseUpdate指向链表尾。
  3. 触发更新后,产生的Update会保存在shared.pending。shared.pending指向第一个Update。shared.pending.next指向最后一个Update。
  4. 开始计算state前会把两个链表连接成新的链表。

示例

React底层架构

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,
};
  1. memoizedState根据不同hook保存的对应的数据。
  2. useState -> state
  3. useReducer -> state
  4. useEffect -> callback, [...deps]
  5. useRef -> {current: initialValue}
  6. useMemo -> [callback(), [...deps]]
  7. useCallback -> [callback, [...deps]]
  8. baseState是参与计算的初始state,如果没有遗留的Upate,那么等于memoizedState,有遗留的Upate会保存中间状态。
  9. baseQueue是本次更新前遗留的Upate,部分Update因为优先级低,在上次render阶段没有参与state的计算。会以链表形式保存。
  10. 触发更新后,产生的Update会保存在queue.pending。queue.pending指向第一个Update。queue.pending.next指向最后一个Update。
  11. 开始计算state前会把两个链表连接成新的链表。

示例

React底层架构

Effect

数据结构

const effect = {
  tag,
  create,
  destroy,
  deps,
  next: null,
};
  1. tag有三个类型
  2. const Insertion = 0b0010; -> useInsertionEffect
  3. const Layout = 0b0100; -> useLayoutEffect
  4. const Passive = 0b1000; -> useEffect
  5. create 回调函数
  6. destroy 组件销毁时执行函数
  7. deps 依赖项
  8. next字段指向当前Fiber的其他Effect,形成环状列表。
// 这里是tag
useEffect(() => {
    // 这里是create
    return () => {
        // 这里是destroy
    }
},[]) // 这里是deps

示例

React底层架构

参考

React设计原理 - 卡颂。

转载自:https://juejin.cn/post/7395866692771627048
评论
请登录