title: setState desc: React中, setState是同步还是异步

order: 1

React 中, setState 是同步还是异步

所谓同步还是异步指的是调用 setState 之后是否马上能得到最新的 state

不仅仅是setState了, 在对 function 类型组件中的 hook 进行操作时也是一样, 最终决定setState是同步渲染还是异步渲染的关键因素是ReactFiberWorkLoop工作空间的执行上下文.

具体代码如下:

  1. export function scheduleUpdateOnFiber(
  2. fiber: Fiber,
  3. expirationTime: ExpirationTime,
  4. ) {
  5. const priorityLevel = getCurrentPriorityLevel();
  6. if (expirationTime === Sync) {
  7. if (
  8. // Check if we're inside unbatchedUpdates
  9. (executionContext & LegacyUnbatchedContext) !== NoContext &&
  10. // Check if we're not already rendering
  11. (executionContext & (RenderContext | CommitContext)) === NoContext
  12. ) {
  13. performSyncWorkOnRoot(root);
  14. } else {
  15. ensureRootIsScheduled(root);
  16. schedulePendingInteractions(root, expirationTime);
  17. if (executionContext === NoContext) {
  18. // Flush the synchronous work now, unless we're already working or inside
  19. // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
  20. // scheduleCallbackForFiber to preserve the ability to schedule a callback
  21. // without immediately flushing it. We only do this for user-initiated
  22. // updates, to preserve historical behavior of legacy mode.
  23. flushSyncCallbackQueue();
  24. }
  25. }
  26. } else {
  27. // Schedule a discrete update but only if it's not Sync.
  28. if (
  29. (executionContext & DiscreteEventContext) !== NoContext &&
  30. // Only updates at user-blocking priority or greater are considered
  31. // discrete, even inside a discrete event.
  32. (priorityLevel === UserBlockingPriority ||
  33. priorityLevel === ImmediatePriority)
  34. ) {
  35. // This is the result of a discrete event. Track the lowest priority
  36. // discrete update per root so we can flush them early, if needed.
  37. if (rootsWithPendingDiscreteUpdates === null) {
  38. rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
  39. } else {
  40. const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
  41. if (
  42. lastDiscreteTime === undefined ||
  43. lastDiscreteTime > expirationTime
  44. ) {
  45. rootsWithPendingDiscreteUpdates.set(root, expirationTime);
  46. }
  47. }
  48. }
  49. // Schedule other updates after in case the callback is sync.
  50. ensureRootIsScheduled(root);
  51. schedulePendingInteractions(root, expirationTime);
  52. }
  53. }

可以看到, 是否同步渲染调度决定代码是flushSyncCallbackQueue(). 进入该分支的条件:

  1. 必须是legacy模式, concurrent模式下expirationTime不会为Sync
  2. executionContext === NoContext, 执行上下文必须要为空.

两个条件缺一不可.

结论

同步:

  1. 首先在legacy模式
  2. 在执行上下文为空的时候去调用setState
    • 可以使用异步调用如setTimeout, Promise, MessageChannel
    • 可以监听原生事件, 注意不是合成事件, 在原生事件的回调函数中执行 setState 就是同步的

异步:

  1. 如果是合成事件中的回调, executionContext |= DiscreteEventContext, 所以不会进入, 最终表现出异步
  2. concurrent 模式下都为异步

演示示例

  1. import React from 'react';
  2. export default class App extends React.Component {
  3. state = {
  4. count: 0,
  5. };
  6. changeState = () => {
  7. const newCount = this.state.count + 1;
  8. this.setState({
  9. count: this.state.count + 1,
  10. });
  11. if (newCount === this.state.count) {
  12. console.log('同步执行render');
  13. } else {
  14. console.log('异步执行render');
  15. }
  16. };
  17. changeState2 = () => {
  18. const newCount = this.state.count + 1;
  19. Promise.resolve().then(() => {
  20. this.setState({
  21. count: this.state.count + 1,
  22. });
  23. if (newCount === this.state.count) {
  24. console.log('同步执行render');
  25. } else {
  26. console.log('异步执行render');
  27. }
  28. });
  29. };
  30. render() {
  31. return (
  32. <div>
  33. <p>当前count={this.state.count}</p>
  34. <button onClick={this.changeState}>异步+1</button>
  35. <button onClick={this.changeState2}>同步+1</button>
  36. </div>
  37. );
  38. }
  39. }

在看一个 concurrent 模式下的例子, 相同的代码都为异步 render:

Edit boring-faraday-m7jtx