React 源码 - Update对象

315 阅读2分钟

1、Update 对象

  • eventTime:任务时间,通过performance.now()获取的毫秒数
  • lane:优先级相关字段
  • suspenseConfig:Suspense相关,暂不关注。
  • tag:更新的类型,包括UpdateState | ReplaceState | ForceUpdate | CaptureUpdate
  • payload:更新挂载的数据
  • callback:更新的回调函数
  • next:与其他Update连接形成链表
const update: Update<*> = {
  eventTime,
  lane,
  suspenseConfig,
  tag: UpdateState,
  payload: null,
  callback: null,
  next: null,
};

2、Update 与 Fiber的联系

  • Fiber节点上的多个Update会组成链表并被包含在fiber.updateQueue

3、updateQueue

  • baseState
    • 本次更新前该Fiber节点state
  • firstBaseUpdatelastBaseUpdate
    • 由于某些Update优先级较低,在上次Update计算state时被跳过
    • 以链表形式存在,链表头为firstBaseUpdate,链表尾为lastBaseUpdate
  • shared.pending
    • 本次更新产生的Update对象
    • 保存在shared.pending中形成单向环状链表
    • 当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面
  • effects:数组,保存update.callback !== nullUpdate
const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
        pending: null,
    },
    effects: null,
};

4、案例:updateQueue的工作流

假设有一个fiber刚经历commit阶段完成渲染。该fiber上有两个由于优先级过低所以在上次的render阶段并没有处理的Update。他们会成为下次更新的baseUpdate

我们称其为u1u2,其中u1.next === u2

fiber.updateQueue.firstBaseUpdate === u1;
fiber.updateQueue.lastBaseUpdate === u2;
u1.next === u2;

// 用`-->`表示链表的指向
fiber.updateQueue.baseUpdate: u1 --> u2

此时fiber上触发两次状态更新,产生两个新的Update,我们称为u3u4

// 当插入`u3`后:
fiber.updateQueue.shared.pending === u3;
u3.next === u3;


// `shared.pending`的环状链表,用图表示为:
fiber.updateQueue.shared.pending:   u3 ─────┐ 
                                     ^      |                                    
                                     └──────┘
                                     
// 接着插入`u4`之后:
fiber.updateQueue.shared.pending === u4;
u4.next === u3;
u3.next === u4;


// `shared.pending`是环状链表,用图表示为:
fiber.updateQueue.shared.pending:   u4 ──> u3
                                     ^      |                                    
                                     └──────┘
                                     
                                                                      
// 调度完成进入`render阶段`
// 此时`shared.pending`的环被剪开并连在`updateQueue.lastBaseUpdate`后面
fiber.updateQueue.baseUpdate: u1 --> u2 --> u3 --> u4


// 接下来遍历`updateQueue.baseUpdate`链表,在遍历时如果有优先级低的`Update`会被跳过

5、更新过程中两个疑问

低优先级的任务被打断后如何保证Update不丢失?
  • shared.pending会被同时连接在 workInProgress updateQueue.lastBaseUpdate 与 current updateQueue.lastBaseUpdate 后面

  • render阶段被中断后重新开始时,会基于 current updateQueue 克隆出 workInProgress updateQueue。由于 current updateQueue.lastBaseUpdate 已经保存了上一次的 Update,所以不会丢失。

  • commit阶段完成渲染,由于 workInProgress updateQueue.lastBaseUpdate 中保存了上一次的 Update,所以 workInProgress Fiber树 变成 current Fiber树 后也不会造成 Update 丢失

如何保证状态依赖的连续性?

优先级:( A1 == C1 ) > ( B2 == D2 )

// 初始状态
baseState: ''
shared.pending: A1 --> B2 --> C1 --> D2


// 第一次 render 调用 A1 --> C1
baseState: ''
baseUpdate: null
memoizedState: 'AC' // 中间值


// 第一次 render 调用 B2 --> C1 --> C2 
baseState: 'A' // 此处为'A',并非 'AC'
baseUpdate: B2 --> 'C1' --> D2 // 'C1'会重新被计算一次
memoizedState: 'ABCD' // 最终值