React 中的 UpdateQueue 详解

Update

面试题:说一说 React 中的 updateQueue

在 React 中,有许多触发状态更新的方法,比如:

  • ReactDOM.createRoot
  • this.setState
  • this.forceUpdate
  • useState dispatcher
  • useReducer dispatcher

虽然这些方法执行的场景会有所不同,但是都可以接入同样的更新流程,原因是因为它们使用同一种数据结构来表示更新,这种数据结构就是 Update。

Update 数据结构

在 React 中,更新实际上是存在优先级的,其心智模型有一些类似于"代码版本管理工具"。

举个例子,假设现在我们在开发一个软件,当前软件处于正常的迭代中,拥有 A、B、C 三个正常需求,此时突然来了一个紧急的线上 Bug,整体流程如下:

为了修复线上 Bug D,你需要先完成需求 A、B、C,之后才能进行 D 的修复,这样的设计实际上是不合理的。

有了代码版本管理工具之后,有紧急线上 Bug 需要修复时,可以先暂存当前分支的修改,在 master 分支修复 Bug D 并紧急上线:

当 Bug 修复完毕后,再正常的来迭代 A、B、C 需求,之后的迭代会基于 D 这个版本:

并发更新的 React 也拥有相似的能力,不同的 update 是有不同的优先级,高优先级的 update 能够中断低优先级的 update,当高优先级的 update 完成更新之后,后续的低优先级更新会在高优先级 update 更新后的 state 的基础上再来进行更新。

接下来我们来看一下 Update 的一个数据结构。

在前面我们说了在 React 中,有不同的触发更新的方法,不同的方法实际上对应了不同的组件:

  • ReactDOM.createRoot 对应 HostRoot
  • this.setState 对应 ClassComponent
  • this.forceUpdate 对应 ClassComponent
  • useState dispatcher 对应 FunctionComponent
  • useReducer dispatcher 对应 FunctionComponent

不同的组件类型,所对应的 Update 的数据结构是不同的。

HostRoot 和 ClassComponent 这一类组件所对应的 Update 数据结构如下:

jsx 复制代码
function createUpdate(eventTime, lane){
  const update = {
    eventTime,
    lane,
        // 区分触发更新的场景
    tag: UpdateState,
    payload: null,
    // UI 渲染后触发的回调函数
    callback: null,
    next: null,
  };
  return update;
}

在上面的 Update 数据结构中,tag 字段是用于区分触发更新的场景的,选项包括:

  • ReplaceState:代表在 ClassComponent 生命周期函数中直接改变 this.state
  • UpdateState:默认情况,通过 ReactDOM.createRoot 或者 this.setState 触发更新
  • CaptureUpdate:代表发生错误的情况下在 ClassComponent 或 HostRoot 中触发更新(比如通过 getDerivedStateFormError 方法)
  • ForceUpdate:代表通过 this.forceUpdate 触发更新

接下来来看一下 FunctionComponent 所对应的 Update 数据结构:

jsx 复制代码
const update = {
  lane,
  action,
  // 优化策略相关字段
  hasEagerState: false,
  eagerState: null,
  next: null
}

在上面的数据结构中,有 hasEagerState 和 eagerState 这两个字段,它们和后面要介绍的 React 内部的性能优化策略(eagerState 策略)相关。

在 Update 数据结构中,有三个问题是需要考虑:

  • 更新所承载的更新内容是什么

对于HostRoot以及类组件来讲,承载更新内容的字段为 payload 字段

jsx 复制代码
// HostRoot
ReactDOM.createRoot(rootEle).render(<App/>);                                   
// 对应 update
{
    payload : {
    // HostRoot 对应的 jsx,也就是 <App/> 对应的 jsx
    element                                  
  },
  // 省略其他字段
}
​
// ClassComponent 情况1
this.setState({num : 1})
// 对应 update
{
  payload : {
    num: 1
  },
  // 省略其他字段
}
​
// ClassComponent 情况2
this.setState({num : num => num + 1})
// 对应 update
{
  payload : {
    num: num => num + 1
  },
  // 省略其他字段
}

对于函数组件来讲,承载更新内容的字段为 action 字段

jsx 复制代码
// FC 使用 useState 情况1
updateNum(1);
// 对应 update
{
  action : 1,
  // 省略其他字段
}
​
// FC 使用 useState 情况2
updateNum(num => num + 1);
// 对应 update
{
  action : num => num + 1,
  // 省略其他字段
}
  • 更新的紧急程度:紧急程度是由 lane 字段来表示的
  • 更新之间的顺序:通过 next 字段来指向下一个 update,从而形成一个链表。

UpdateQueue

上面所介绍的 update 是计算 state 的最小单位,updateQueue 是由 update 组成的一个链表,updateQueue 的数据结构如下:

jsx 复制代码
const updateQueue = {
  baseState: null,
  firstBaseUpdate: null,
  lastBaseUpdate: null,
  shared: {
    pending: null
  }
}
  • baseState:参与计算的初始 state,update 基于该 state 计算新的 state,可以类比为心智模型中的 master 分支。
  • firstBaseUpdate 与 lastBaseUpdate:表示更新前该 FiberNode 中已保存的 update,以链表的形式串联起来。链表头部为 firstBaseUpdate,链表尾部为 lastBaseUpdate。
  • shared.pending:触发更新后,产生的 update 会保存在 shared.pending 中形成单向环状链表。计算 state 时,该环状链表会被拆分并拼接在 lastBaseUpdate 后面。

举例说明,例如当前有一个 FiberNode 刚经历完 commit 阶段的渲染,该 FiberNode 上面有两个"由于优先级低,导致在上一轮 render 阶段并没有被处理的 update",假设这两个 update 分别名为 u0 和 u1

jsx 复制代码
fiber.updateQueue.firstBaseUpdate = u0;
fiber.updateQueue.lastBaseUpdate = u1;
u0.next = u1;

那么假设在当前的 FiberNode 上面我们又触发了两次更新,分别产生了两个 update(u2 和 u3),新产生的 update 就会形成一个环状链表,shared.pending 就会指向这个环状链表,如下图所示:

之后进入新的一轮 render,在该 FiberNode 的 beginWork 中,shared.pending 所指向的环状链表就会被拆分,拆分之后接入到 baseUpdate 链表后面:

接下来就会遍历 updateQueue.baseUpdate,基于 updateQueue.baseState 来计算每个符合优先级条件的 update(这个过程有点类似于 Array.prototype.reduce),最终计算出最新的 state,该 state 被称之为 memoizedState。

因此我们总结一下,整个 state 的计算流程可以分为两步:

  • 将 shared.pending 所指向的环状链表进行拆分并且和 baseUpdate 进行拼接,形成新的链表
  • 遍历连接后的链表,根据 wipRootRenderLanes 来选定优先级,基于符合优先级条件的 update 来计算 state

真题解答

题目:面试题:说一说 React 中的 updateQueue

参考答案:

update 是计算 state 的最小单位,一条 updateQueue 代表由 update 所组成的链表,其中几个重要的属性列举如下:

  • baseState:参与计算的初始 state,update 基于该 state 计算新的 state,可以类比为心智模型中的 master 分支。
  • firstBaseUpdate 与 lastBaseUpdate:表示更新前该 FiberNode 中已保存的 update,以链表的形式串联起来。链表头部为 firstBaseUpdate,链表尾部为 lastBaseUpdate。
  • shared.pending:触发更新后,产生的 update 会保存在 shared.pending 中形成单向环状链表。计算 state 时,该环状链表会被拆分并拼接在 lastBaseUpdate 后面。

整个 state 的计算流程可以分为两步:

  • 将 shared.pending 所指向的环状链表进行拆分并且和 baseUpdate 进行拼接,形成新的链表
  • 遍历连接后的链表,根据 wipRootRenderLanes 来选定优先级,基于符合优先级条件的 update 来计算 state

以下是简易版理解方式

想象一下,你有一个玩具盒子,里面有好多小玩具。这些小玩具就像是 React 里的"更新"(Update),它们会告诉 React 怎么去改变一个组件(就像一个玩具)的状态。这些更新会被放在一个队列(UpdateQueue)里,就像排队等着被处理的小玩具。

UpdateQueue 是什么?

UpdateQueue 就是一个用来管理这些更新的"盒子"。它有几部分:

  • baseState:这是最开始的状态,就像你刚拿到玩具时的样子。
  • firstBaseUpdate 和 lastBaseUpdate:这是已经排队等着的更新,就像已经排好队的小朋友。
  • shared.pending:这是新来的更新,它们会先围成一个圈(环状链表),等轮到它们的时候再加入队列。

更新是怎么工作的?

当 React 要更新一个组件时,它会做两件事:

  1. 把新来的更新(shared.pending)从圈圈里拿出来,加到已经排队的更新后面。
  2. 然后,React 会按照优先级(比如紧急的先处理)一个个看这些更新,用它们来改变组件的状态。

举个例子

假设你有一个积木,最开始是红色的(baseState)。然后你决定把它涂成蓝色(第一个更新),接着又想在上面画个小星星(第二个更新)。这些更新都会被放进 UpdateQueue 里。React 会按照顺序处理这些更新,最后积木就变成了你想要的样子。

React 源代码里的 UpdateQueue

在 React 的代码里,UpdateQueue 是用一个对象表示的,就像一个盒子。这个盒子有几部分,比如 baseStatefirstBaseUpdatelastBaseUpdateshared.pending。每次你调用 setState 或者其他更新方法时,React 就会往这个盒子里放一个新的更新。

jsx 复制代码
const updateQueue = {
  baseState: null, // 最开始的状态
  firstBaseUpdate: null, // 第一个更新
  lastBaseUpdate: null, // 最后一个更新
  shared: {
    pending: null // 新来的更新
  }
};

所以,UpdateQueue 就是一个用来管理更新的工具,它帮助 React 按顺序处理这些更新,让组件的状态能够正确地改变。就像你排队等玩具一样,React 也会按照顺序处理这些更新。

相关推荐
前端付豪几秒前
1、为什么浏览器要有渲染流程? ——带你一口气吃透 Critical Rendering Path
前端·后端·浏览器
前端付豪3 分钟前
3、Node.js异步编程彻底吃透
前端·后端·node.js
孤鸿玉8 分钟前
[Flutter小试牛刀] 低配版signals,添加局部刷新
前端·flutter
亦黑迷失9 分钟前
轻量级 Express 服务器:用 Pug 模板引擎实现动态参数传递
前端·javascript·后端
吃瓜群众i1 小时前
理解Javascript闭包
前端·javascript
安大桃子1 小时前
Mapbox GL + Deck.gl 三维实战:Mapbox 加载 Tileset3D 倾斜摄影模型
前端·webgl
yede1 小时前
多行文本省略号显示,更多按钮展开全部
前端
就是我1 小时前
React 应用性能优化实战
前端·react.js·性能优化
G扇子1 小时前
深入解析XSS攻击:从原理到防御的全方位指南
前端·安全