第 18 题:React 中 setState 什么时候是同步的,什么时候是异步的?

发布于 2022-11-12 15:39:00 字数 968 浏览 158 评论 17

在 React 中,如果是由 React 引发的事件处理(比如通过 onClick 引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的 setState 调用会同步执行 this.state 。所谓 除此之外,指的是绕过 React 通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval 产生的异步调用。

原因: 在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由React控制的事件处理过程 setState 不会同步更新this.state

注意: setState 的 异步 并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的 异步,当然可以通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。

详细请看 深入 setState 机制

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

被你宠の有点坏 2022-05-04 13:57:35

react 18 发布之后,估计要对不同版本不同模式进行不同的回答了

国粹 2022-05-04 13:57:35

为什么setTimeout里面的setState就不能进行批处理?如果批处理会有什么异常,望解答。

皓月长歌 2022-05-04 13:57:33

setState是异步的,setState之后,取state中的值并不一定是最新更新的值。
这样设计的原因是可能在同步的代码中可能存在连续的多个setSate操作,react会对他们进行智能的合并,直到执行到了最后一个setState,React才回智能的合并state的,并异步的设置state的值,后续判断是否进行render操作.如果同步的每次setState都去走一遍reconciler & commit操作,则太耗性能了

晌融 2022-05-04 13:57:06

我们知道Promise.then(),setTimeout是异步执行. 从js执行来说, setState肯定是同步执行.

所以这里讨论的同步和异步并不是指setState是否异步执行, 而是指调用setState之后this.state能否立即更新.

維他命╮ 2022-05-04 13:56:48

setState是同步还是异步?

基于当前最新稳定版本v16.14.0进行分析

此处同步异步的定义

我们知道Promise.then(),setTimeout是异步执行. 从js执行来说, setState肯定是同步执行.

所以这里讨论的同步和异步并不是指setState是否异步执行, 而是指调用setState之后this.state能否立即更新.

分析

  1. 众所周知调用setState({item: 'new xxx'})之后, 会将传入setState的参数包装成一个update对象并添加到updateQueue队列中.
  2. 之后updateQueue队列在什么时机被合并到this.state中才是本题目的关键. 因为合并之后this.state必然就已经更新了.
  3. state的合并是在fiber构建循环中进行的, 而fiber构建循环必然是在触发scheduler调度之后进行. 关于这一点的详细论述可以参考react两大工作循环.

到这里问题转化为调用setState之后, 是否立即触发scheduler调度?

  • 如果立即进行scheduler调度, 那么this.state必然能同步获取.
  • 反之, 如果异步进行scheduler调度, 那么this.state不能同步获取.
  1. 每次调用setState都会进行一次scheduler调度(可以参考React 调度机制).
    在最新源码v16.14.0中体现为调用ensureRootIsScheduled. 在该源码中, 可以得到回答本题目的最佳答案, 核心逻辑如下:
 if (
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
       // .... 省略部分本次讨论不会涉及的代码
    } else {
      ensureRootIsScheduled(root, eventTime); // 触发scheduler调度(调度是异步的) , 所以该函数不会立即触发render.
      if (executionContext === NoContext) {  // 当执行上下文为0时, 会刷新同步队列
         // .... 省略部分本次讨论不会涉及的代码

        // 这里是关键,  执行同步回调队列. 有兴趣的同学可以继续在源码中查看, 可以得到结论:
        // if分支之外的ensureRootIsScheduled(root, eventTime)和此处的flushSyncCallbackQueue()
        // 最终都是调用performSyncWorkOnRoot进行fiber树的循环构建
        flushSyncCallbackQueue(); 
      }
    }

结论

  1. 如楼上 @taichiyi 所述, setState是同步和异步最关键的因素是react内部的执行上下文executionContext的状态.
  • executionContext为空时, 表现为同步.
  • 反之executionContext不为空, 表现为异步.
  1. executionContext何时为空?

这个问题反过来更好理解, 什么时候executionContext不为空? 因为executionContext是react内部控制的属性, 当初次render, 合成事件触发时都会改变executionContext的值.

  1. 只要绕开react内部触发更改executionContext的逻辑, 就能保证executionContext为空, 进而实现setState为同步.
  • 可以使用异步调用如setTimeout, Promise, MessageChannel等
  • 可以监听原生事件, 注意不是合成事件(合成事件是react体系, 会更改executionContext), 在原生事件的回调函数中执行 setState 就是同步的

附加条件

以上分析都是基于legacy模式进行分析的, 众所周知react即将(可能)全面进入concurrent模式(可以参考react 启动模式). 在concurrent模式下, 这个题目可能就没有意义了, 因为从目前最新代码来看, 在concurrent模式下根本就不会判断executionContext, 所以concurrent模式下setState都为异步.

 // concurrent模式下根本没有下列代码, 所以不可能同步
if (executionContext === NoContext) { 
        flushSyncCallbackQueue(); 
      }

演示示例

对于此问题在codesandbox上面做了一个demo(详细演示示例). 在legacyconcurrent模式下演示并验证了上述结论.

软糖 2022-05-04 13:56:36

先说结论:

如果是通过 DOM 事件(例如:onClick) 的事件处理函数调用了 setState 函数,导致 React 进行更新(或者说“调度”scheduleWork),则此时事件处理函数中的 setState 的行为是异步的(也就是合并更新)。

上面的评论中说了很多 setState “异步”的情况。我这里补充一下“同步”的情况,下面的例子使用了 setTimeOut,组件最后显示结果是33

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.handleAddCount = this.handleAddCount.bind(this);
  }
  componentDidMount() {
    setTimeout(this.handleAddCount, 1*1000);
  }
  handleAddCount() {
    this.setState({
      count: this.state.count + 11
    });
    this.setState({
      count: this.state.count + 22
    });
  }
  render() {
    return <div>{this.state.count}</div>;
  }
}

源码:

从源码的角度来说,setState 的行为是“异步”还是“同步”取决于 React 执行 setState 方法时的执行上下文①(ExecutionContext)。

如果 ExecutionContext 为 0,表示当前没有正在进行的其他任务,则 setState 是“同步”的。React 源码地址:https://github.com/facebook/react/blob/b53ea6ca05d2ccb9950b40b33f74dfee0421d872/packages/react-reconciler/src/ReactFiberWorkLoop.js

①注意这里的执行上下文不是浏览器的,为了更好的控制渲染任务,避免长时间占用浏览器的主线程, React 实现了自己的执行上下文

金兰素衣 2022-05-04 13:50:41

React的setState本身并不是异步的,是因为其批处理机制给人一种异步的假象。

【React的更新机制】

生命周期函数和合成事件中:

  1. 无论调用多少次setState,都不会立即执行更新。而是将要更新的state存入'_pendingStateQuene',将要更新的组件存入'dirtyComponent';
  2. 当根组件didMount后,批处理机制更新为false。此时再取出'_pendingStateQuene'和'dirtyComponent'中的state和组件进行合并更新;

原生事件和异步代码中:

  1. 原生事件不会触发react的批处理机制,因而调用setState会直接更新;
  2. 异步代码中调用setState,由于js的异步处理机制,异步代码会暂存,等待同步代码执行完毕再执行,此时react的批处理机制已经结束,因而直接更新。

总结:
react会表现出同步和异步的现象,但本质上是同步的,是其批处理机制造成了一种异步的假象。(其实完全可以在开发过程中,在合成事件和生命周期函数里,完全可以将其视为异步)

幸福%小`乖 2022-05-04 13:45:40

异步更新,同步执行

难如初 2022-05-04 13:21:42

单独的说同步异步没有意义,一般配合场景:setState后取值使用的,初学者经常发现setState后值还是旧的,而且这个错误很难发现。

旧人九事 2022-05-04 13:17:54

由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。
React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。

愿与i 2022-05-04 13:02:17

React Dan Abramov 大佬的解释
这题应该是想问 setStatereconiliation 特性

喜爱纠缠 2022-05-04 12:38:17

@yygmind 被这问题吓到了,还好点进来看了,你做出了我放心的解释

肥爪爪 2022-05-04 11:54:50

我的个人观点是这样的对于同步或者是异步都可以进行控制的想要同步就同步想要异步就异步
我老婆提醒我如果面试官问什么你管不了 谁让你想去那工作呢

望喜 2022-05-03 23:56:57

我们平时写的时候就当它是异步进行就好了。

以为你会在 2022-05-03 15:36:06

这道题和19题有些类似。#18 (comment)

顾忌 2022-05-02 23:55:13

这里所说的同步异步, 并不是真正的同步异步, 它还是同步执行的。
这里的异步指的是多个state会合成到一起进行批量更新。
希望初学者不要被误导

你的说法还是太片面了,可以看下官网的介绍


https://reactjs.org/docs/faq-state.html

你是想说明什么,没太明白。

你的意思难道是从你截图给的这个可以看出reactsetState是或者部分是异步的?

---补充---

或者你的意思难道是标题是《何时setState是异步的》, 就断定 “setState确实可能是异步的”

遥远的绿洲 2022-05-01 03:06:41

这里所说的同步异步, 并不是真正的同步异步, 它还是同步执行的。

这里的异步指的是多个state会合成到一起进行批量更新。

希望初学者不要被误导

~没有更多了~

关于作者

櫻之舞

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

已经忘了多久

文章 0 评论 0

15867725375

文章 0 评论 0

LonelySnow

文章 0 评论 0

走过海棠暮

文章 0 评论 0

轻许诺言

文章 0 评论 0

信馬由缰

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文