05-React 状态更新

原文链接
首先我们先大体看一下状态更新的整个调用路径的关键节点:
在这里插入图片描述

Update&updateQueue

状态更新流程开始后首先会创建 Update 对象,所以首先我们需要了解 Update 的结构与工作流程。
首先,触发更新的方式主要有以下几种:

  • ReactDOM.render —— HostRoot
  • this.setState —— ClassComponent
  • this.forceUpdate —— ClassComponent
  • useState —— FunctionComponent
  • useReducer —— FunctionComponent

由于不同类型组件工作方式不同,所以存在两种不同结构的 Update,其中 ClassComponent 与 HostRoot 共用一套Update 结构,FunctionComponent 单独使用一种 Update 结构;虽然他们的结构不同,但是他们工作机制与工作流程大体相同。有关 hooks 的内容,会专门写一篇 hooks 相关的代码解析。

Update 的结构

ClassComponent 与 HostRoot(即 rootFiber.tag 对应类型)共用同一种 Update 结构。HostRoot或者ClassComponent 触发更新后,会在函数 createUpdate 中创建 update,并在后面的 render 阶段的 beginWork 中计算 Update。
Update 对应的结构如下:

const update: Update<*> = {
   
   
  eventTime,
  lane,
  tag: UpdateState,
  payload: null,
  callback: null,
  next: null,
};

我们主要关注这些参数:

  • eventTime:任务时间,通过 performance.now() 获取的毫秒数。
  • lane:优先级相关字段。
  • tag:更新的类型,包括 UpdateState | ReplaceState | ForceUpdate | CaptureUpdate。
  • payload:更新挂载的数据,不同类型组件挂载的数据不同。对于 ClassComponent,payload 为 this.setState的第一个传参。对于 HostRoot,payload 为 ReactDOM.render 的第一个传参。
  • callback:更新的回调函数,也就是 setState 的第二个参数。
  • next:与其他 Update 连接形成链表,例如如果同时触发多个 setState 时会形成多个 Update,然后通过 next 连接。

注意这里的 lane 属性是表示当前更新的优先级,react 中使用 Lane 多车道优先级模型,后面会写一篇有关 Lane 模型相关的文章。

updateQueue

类似 Fiber 节点组成 Fiber 树,Fiber 节点上的多个 Update 会组成链表并被包含在 fiber.updateQueue 中。
ClassComponent 与 HostRoot 使用的 UpdateQueue 结构如下:

const queue: UpdateQueue<State> = {
   
   
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
   
   
      pending: null,
      interleaved: null,
      lanes: NoLanes
    },
    effects: null,
  };

字段说明如下:

  • baseState:本次更新前该 Fiber 节点的 state,Update 基于该 state 计算更新后的 state。
  • firstBaseUpdate 与 lastBaseUpdate:本次更新前该 Fiber 节点已保存的 Update。以链表形式存在,链表头为 firstBaseUpdate,链表尾为 lastBaseUpdate。之所以在更新产生前该 Fiber 节点内就存在 Update,是由于某些 Update 优先级较低所以在上次 render 阶段由 Update 计算 state 时被跳过。
  • shared.pending:新产生的 Update 会以单向环状链表保存在 shared.pending 中。当由 Update 计算 state 时这个环会被剪开并连接在 lastBaseUpdate 后面。
  • shared.interleaved:这种 update 是指渲染过程中的 update,通常由并发的用户输入事件引起,在当前渲染结束后,交错更新队列会被推进 pending 队列;interleaved 也是类似 pending 的单向链表数据结构。
  • effects:数组。保存 update.callback !== null 的 Update。

对于 HostRoot 或者 ClassComponent 会在 mount 的时候使用 initializeUpdateQueue 创建 updateQueue,然后将 updateQueue 挂载到 fiber 节点上:

export function initializeUpdateQueue<State>(fiber: Fiber): void {
   
   
  const queue: UpdateQueue<State> = {
   
   
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
   
   
      pending: null,
      interleaved: null,
      lanes: NoLanes
    },
    effects: null,
  };
  fiber.updateQueue = queue;
}

enqueueUpdate 用来将 update 加入 updateQueue 队列:

export function enqueueUpdate<State>(
  fiber: Fiber, // fiberRoot
  update: Update<State>,
  lane: Lane,
) {
   
   
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
   
   
    // Only occurs if the fiber has been unmounted.
    return;
  }
  // 当前队列和缓存队列共享一个持久化队列
  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
  // 比较 fiber lane 和 lane,相同时更新
  // render 初始化时不执行
  if (isInterleavedUpdate(fiber, lane)) {
   
   
    const interleaved = sharedQueue.interleaved;// 交错更新
    if (interleaved === null) {
   
   
      // 如果是第一次更新,创建双向链表
      update.next = update;
      // 在当前渲染结束时,将显示此队列的交错更新
      // 被转移到挂起队列。
      pushInterleavedQueue(sharedQueue);
    } else {
   
   
      // interleaved.next ->  update.next   update - interleaved.next;
      // interleaved.next = update
      // update.next = interleaved.next = update
      update.next = interleaved.next;
      interleaved.next = update;
    }
    sharedQueue.interleaved = update;
  } else {
   
   
    const pending = sharedQueue.pending;
    if (pending === null) {
   
   
      // 这是第一次更新。创建单向链表
      update.next = update;
    } else {
   
   
      // 定义双向列表
      update.next = pending.next;
      pending.next = update;
    }
    sharedQueue.pending = update;
  }
}

InterleavedUpdate

InterleavedUpdate 是进行交错更新的处理,在渲染过程中途出现的 update,被称为交错更新,在更新队列中,有两个单链表队列字段:pending 和 interleaved 。在我们调度一个交错更新 update 时,它会被储存在 interleaved 属性中。然后整个字段都会被推送到一个全局数组变量上。在当前渲染结束之后,遍历全局数组变量,将交错更新转移到 pending 队列中。

深入理解优先级

什么是优先级

状态更新由用户交互产生,用户心里对交互执行顺序有个预期。React 根据人机交互研究的结果中用户对交互的预期顺序为交互产生的状态更新赋予不同优先级。
具体如下:

  • 生命周期方法:同步执行。
  • 受控的用户输入:比如输入框内输入文字,同步执行。
  • 交互事件:比如动画,高优先级执行。
  • 其他:比如数据请求,低优先级执行。

优先级机制

React 中有三套优先级机制:

  1. React 事件优先级;
  2. Lane 优先级;
  3. Scheduler 优先级;

React 事件优先级

// 离散事件优先级,例如:点击事件,input 输入等触发的更新任务,优先级最高
export const DiscreteEventPriority: EventPriority = SyncLane;
// 连续事件优先级,例如:滚动事件,拖动事件等,连续触发的事件
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默认事件优先级,例如:setTimeout 触发的更新任务
export const DefaultEventPriority: EventPriority = DefaultLane;
// 闲置事件优先级,优先级最低
export const IdleEventPriority: EventPriority = IdleLane;

可以看到 React 的事件优先级的值还是使用的 Lane 的值,这里不直接使用 Lane 也是为了不与 Lane 机制耦合;Scheduler 是一个单独的包,可以用在其他需要任务调度的场景中。

Lane 优先级转换为 React 事件优先级

export function lanesToEventPriority(lanes: Lanes): EventPriority {
   
   
  // 找到优先级最高的lane
  const lane = getHighestPriorityLane(lanes);
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
   
   
    return DiscreteEventPriority;
  }
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
   
   
    return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) {
   
   
    return DefaultEventPriority;
  }
  return IdleEventPriority;
}

Scheduler 优先级

export const NoPriority = 0; // 没有优先级
export const ImmediatePriority = 1; // 立即执行任务的优先级,级别最高
export const UserBlockingPriority = 2; // 用户阻塞的优先级
export const NormalPriority = 3; // 正常优先级
export const LowPriority = 4; // 较低的优先级
export const IdlePriority = 5; // 优先级最低,闲表示任务可以闲置

React 事件优先级转换为 Scheduler 优先级

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
   
   
    ...
    switch (lanesToEventPriority(nextLanes)) {
   
   
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
}

lanesToEventPriority 函数就是上面 Lane 优先级转换为 React 事件优先级的函数,先将 lane 的优先级转换为 React事件的优先级,然后再根据 React 事件的优先级转换为 Scheduler 的优先级。

Lane 优先级

// lane使用 31 位二进制来表示优先级车道共31条, 位数越小(1的位置越靠右)表示优先级越高
export const TotalLanes = 31;

// 没有优先级
export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

// 同步优先级,表示同步的任务一次只能执行一个,例如:用户的交互事件产生的更新任务
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

// 连续触发优先级,例如:滚动事件,拖动事件等
export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = /*            */ 0b0000000000000000000000000000100;

// 默认优先级,例如使用setTimeout,请求数据返回等造成的更新
export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = /*                    */ 0b0000000000000000000000000010000;

// 过度优先级,例如: Suspense、useTransition、useDeferredValue等拥有的优先级
const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /*                       */ 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiegiser#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值