【问题标题】:Why is setState in reactjs Async instead of Sync?为什么 reactjs Async 中的 setState 而不是 Sync?
【发布时间】:2016-07-05 06:53:30
【问题描述】:

我刚刚发现在reactthis.setState()任何组件中的函数都是异步的,或者是在调用它的函数完成后调用的。

现在我搜索并找到了这个博客(setState() State Mutation Operation May Be Synchronous In ReactJS

在这里他发现setState 是异步的(当堆栈为空时调用)或同步(一调用就调用)取决于状态变化是如何触发的。

现在这两件事很难消化

  1. 在博客中,setState 函数在函数 updateState 内被调用,但触发 updateState 函数的原因并不是被调用函数所知道的。
  2. 他们为什么要让setState 异步,因为 JS 是单线程语言,而且这个 setState 不是 WebAPI 或服务器调用,所以只能在 JS 的线程上完成。他们这样做是为了让重新渲染不会停止所有事件侦听器和东西,还是存在其他一些设计问题。

【问题讨论】:

标签: javascript multithreading asynchronous reactjs


【解决方案1】:

您可以在状态值更新后调用函数:

this.setState({foo: 'bar'}, () => { 
    // Do something here. 
});

另外,如果您有很多状态要同时更新,请将它们全部分组到同一个 setState

代替:

this.setState({foo: "one"}, () => {
    this.setState({bar: "two"});
});

这样做:

this.setState({
    foo: "one",
    bar: "two"
});

【讨论】:

  • 好吧,我们有一个可以使用的回调函数,但不是问题。
  • 希望它能帮助其他人如何偶然发现这个问题。
  • 你有帮助
【解决方案2】:

1) setState 操作是异步的,并且为了提高性能而进行批处理。这在setState的文档中有解释。

setState() 不会立即改变 this.state 而是创建一个挂起的状态转换。在调用此方法后访问 this.state 可能会返回现有值。 无法保证 setState 调用的同步操作,并且调用可能会被批处理以提高性能。


2) 为什么他们要让 setState 异步,因为 JS 是单线程语言,而这个 setState 不是 WebAPI 或服务器调用?

这是因为setState 改变了状态并导致重新渲染。这可能是一项昂贵的操作,并且使其同步可能会使浏览器无响应。

因此,setState 调用是异步的,并且是批处理的,以获得更好的 UI 体验和性能。

【讨论】:

  • 如果您需要在 setState 调用后确保事件的顺序,您可以传递一个回调函数。 this.setState({ something: true }, () => console.log(this.state))
  • 感谢@Sachin 的解释。不过我还是有疑问,能不能像博文解释的那样同步?
  • 反应中的另一个愚蠢的设计决定。使状态更新同步,渲染异步。您可以批量渲染,但我希望能够设置像状态变量这样原始的东西,而不必处理竞争条件。
  • 为什么不允许设置一个选项来使函数异步或同步?这将是一个有用的功能
  • @StephanBijzitter 状态是唯一的真相来源,不是渲染,状态不应该撒谎。渲染可能会滞后 - 它只是真相的视觉表示,而不是真相的来源。如果需要,您可以在状态和渲染之间显式同步。
【解决方案3】:

我知道这个问题很老了,但是很长一段时间以来它一直给许多 reactjs 用户带来很多困惑,包括我。 最近 Dan Abramov(来自 React 团队)刚刚写了一个很好的解释,解释了为什么 setState 的性质是异步的:

https://github.com/facebook/react/issues/11527#issuecomment-360199710

setState 是异步的,在 Dan Abramov 的链接解释中有一些非常好的理由。这并不意味着它会总是是异步的——它主要意味着你不能依赖它是同步的。 ReactJS 在您更改状态的场景中考虑了许多变量,以决定何时应该实际更新 state 并重新渲染您的组件。
一个简单的例子来证明这一点,如果你调用setState 作为对用户操作的反应,那么state 可能会立即更新(尽管你不能指望它),所以用户不会感到任何延迟,但是如果您调用 setState 来响应 ajax 调用响应或其他一些不是由用户触发的事件,那么状态可能会稍微延迟更新,因为用户不会'真的感觉不到这种延迟,它会通过等待将多个状态更新一起批处理并减少重新渲染 DOM 的次数来提高性能。

【讨论】:

  • 你没有将任何答案标记为正确的答案。人们正在发布如何绕过它。不是所问问题的答案。这篇文章看起来不错。
  • @Anup 答案比“异步”或“同步”要复杂一些。它应该始终被视为“异步”,但在某些情况下可能会表现为“同步”。我希望我能给你一些启发。
【解决方案4】:

好文章在这里https://github.com/vasanthk/react-bits/blob/master/patterns/27.passing-function-to-setState.md

// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3

Solution
this.setState((prevState, props) => ({
  count: prevState.count + props.increment
}));

或传递回调this.setState ({.....},callback)

https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82 https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b

【讨论】:

    【解决方案5】:

    您可以使用以下包装进行同步调用

    this.setState((state =>{
      return{
        something
      }
    })

    【讨论】:

    • 被低估的答案
    • 传递回调并不会改变 setState 是异步的事实。
    【解决方案6】:

    是的,setState() 是异步的。

    来自链接:https://reactjs.org/docs/react-component.html#setstate

    • React 不保证立即应用状态更改。
    • setState() 并不总是立即更新组件。
    • 将 setState() 视为更新组件的请求而不是立即命令。

    因为他们认为
    来自链接:https://github.com/facebook/react/issues/11527#issuecomment-360199710

    ...我们同意 setState() 同步重新渲染在许多情况下效率低下

    异步 ​​setState() 使那些刚开始甚至不幸经历过的人的生活变得非常困难:
    - 意外的渲染问题:延迟渲染或不渲染(基于程序逻辑)
    - 传递参数很重要
    其他问题。

    以下示例有所帮助:

    // call doMyTask1 - here we set state
    // then after state is updated...
    //     call to doMyTask2 to proceed further in program
    
    constructor(props) {
        // ..
    
        // This binding is necessary to make `this` work in the callback
        this.doMyTask1 = this.doMyTask1.bind(this);
        this.doMyTask2 = this.doMyTask2.bind(this);
    }
    
    function doMyTask1(myparam1) {
        // ..
    
        this.setState(
            {
                mystate1: 'myvalue1',
                mystate2: 'myvalue2'
                // ...
            },    
            () => {
                this.doMyTask2(myparam1); 
            }
        );
    }
    
    function doMyTask2(myparam2) {
        // ..
    }
    

    希望对您有所帮助。

    【讨论】:

    • 他们为什么不解释为什么他们没有使渲染异步并将值设置为同步?对我来说毫无意义。
    【解决方案7】:

    想象一下在某个组件中增加一个计数器:

      class SomeComponent extends Component{
    
        state = {
          updatedByDiv: '',
          updatedByBtn: '',
          counter: 0
        }
    
        divCountHandler = () => {
          this.setState({
            updatedByDiv: 'Div',
            counter: this.state.counter + 1
          });
          console.log('divCountHandler executed');
        }
    
        btnCountHandler = () => {
          this.setState({
            updatedByBtn: 'Button',
            counter: this.state.counter + 1
          });
          console.log('btnCountHandler executed');
        }
        ...
        ...
        render(){
          return (
            ...
            // a parent div
            <div onClick={this.divCountHandler}>
              // a child button
              <button onClick={this.btnCountHandler}>Increment Count</button>
            </div>
            ...
          )
        }
      }
    

    父组件和子组件都有一个计数处理程序。这是有意完成的,因此我们可以在同一个点击事件冒泡上下文中执行两次 setState(),但在 2 个不同的处理程序中。

    正如我们想象的那样,按钮上的单击事件现在将触发这两个处理程序,因为事件在冒泡阶段从目标冒泡到最外层的容器。

    因此 btnCountHandler() 先执行,预期将计数增加到 1,然后 divCountHandler() 执行,预期将计数增加到 2。

    但是,您可以在 React Developer 工具中检查,计数只会增加到 1。

    这证明反应了

    • 对所有 setState 调用进行排队

    • 在执行完上下文中的最后一个方法(本例中为 divCountHandler)后返回此队列

    • 将发生在同一上下文中的多个 setState 调用中的所有对象突变(例如,单个事件阶段内的所有方法调用都是相同的上下文)合并到一个单一的对象突变语法中(合并是有意义的,因为这就是为什么我们可以首先在 setState() 中独立更新状态属性)

    • 并将其传递给一个单独的 setState() 以防止由于多次 setState() 调用而重新渲染(这是对批处理的非常原始的描述)。

    react 运行的结果代码:

    this.setState({
      updatedByDiv: 'Div',
      updatedByBtn: 'Button',
      counter: this.state.counter + 1
    })
    

    为了阻止这种行为,不是将对象作为参数传递给 setState 方法,而是传递回调。

        divCountHandler = () => {
              this.setState((prevState, props) => {
                return {
                  updatedByDiv: 'Div',
                  counter: prevState.counter + 1
                };
              });
              console.log('divCountHandler executed');
            }
    
        btnCountHandler = () => {
              this.setState((prevState, props) => {
                return {
                  updatedByBtn: 'Button',
                  counter: prevState.counter + 1
                };
              });
          console.log('btnCountHandler executed');
        }
    

    最后一个方法执行完成后,当 react 返回处理 setState 队列时,它只是为每个排队的 setState 调用回调,并传入之前的组件状态。

    这种方式 react 确保队列中的最后一个回调能够更新其所有先前对应物已处理的状态。

    【讨论】:

      【解决方案8】:

      setState 是异步的。你可以在 Reactjs 的这个文档中看到

      React 故意“等待”,直到所有组件在其事件处理程序中调用 setState() 后再开始重新渲染。这通过避免不必要的重新渲染来提高性能。

      但是,您可能仍然想知道为什么 React 不立即更新 this.state 而不重新渲染。

      原因是这会破坏 props 和 state 之间的一致性,从而导致难以调试的问题。

      如果依赖于状态值的变化,你仍然可以执行函数:

      选项 1: 使用带有 setState 的回调函数

      this.setState({
         value: newValue
      },()=>{
         // It is an callback function.
         // Here you can access the update value
         console.log(this.state.value)
      })
      

      选项 2:使用componentDidUpdate 每当该特定类的状态发生变化时,都会调用此函数。

      componentDidUpdate(prevProps, prevState){
          //Here you can check if value of your desired variable is same or not.
          if(this.state.value !== prevState.value){
              // this part will execute if your desired variable updates
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2021-08-11
        • 2021-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-27
        • 2018-04-29
        相关资源
        最近更新 更多