为什么 setState 是异步的

566 阅读1分钟

从 React 的设计来说,setState 分为两个阶段:创建更新、执行更新。

创建更新:setState 首先会创建一个 update,并将 update 入队到当前 fiber.updateQueue 中。然后在 ensureRootIsSchuled 中 schedule(scheduleSyncCallback 或 scheduleCallback) 一个任务。

执行更新:在未来的某个时刻(此时调用 setState 的函数已经执行完)执行第一阶段 schedule 的任务(performSyncWorkOnRoot 或 performConcurrentWorkOnRoot)。

update 的数据结构以及入队过程

多次调用 setState 会在 currentFiber 上形成一条链表,pending 指向链表的最后一个节点,pending.next 指向链表第一个节点。就是说其实 updateQueue 是一个链环,这样可以很方便地找到链表头。

export type Update<State> = {
  // TODO: Temporary field. Will remove this by storing a map of
  // transition -> event time on the root.
  eventTime: number,
  lane: Lane,

  tag: 0 | 1 | 2 | 3, // UpdateState | ReplaceState | ForceUpdate | CaptureUpdate 
  payload: any, // setState 第一个参数
  callback: (() => mixed) | null, // setState 第二个参数

  next: Update<State> | null,
};
const pending = sharedQueue.pending;
if (pending === null) {
  // This is the first update. Create a circular list.
  update.next = update;
} else {
  update.next = pending.next;
  pending.next = update;
}
sharedQueue.pending = update;

一个让 setState 看上去像同步的方法

setStateAsync(state) {
  return new Promise((resolve) => {
    this.setState(state, resolve)
  });
}

async onClick() {
  await this.setStateAsync({count: 0})
  console.log(this.state.count)
}

Class components 的一个实现怪癖也能令 setState 同步

handleClick = () => {
  setTimeout(() => {
    this.setState(({ count }) => ({ count: count + 1 }));

    // { count: 1, flag: false }
    console.log(this.state);

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};

Until React 18, we only batched updates during the React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default.

在 React 18 之前,React 只会对写在 React event handlers 里面的 setState 进行批量更新。对于写在 promises,setTimeout,native event handlers,或者其他事件里面的 setState 则不会

原文链接