首页 文章

为什么setState in reactjs Async而不是Sync?

提问于
浏览
78

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

现在我搜索并找到了这个博客(http://www.bennadel.com/blog/2893-setstate-state-mutation-operation-may-be-synchronous-in-reactjs.htm

在这里,他发现 setState 是异步(在堆栈为空时调用)或同步(调用后立即调用),具体取决于触发状态变化的方式 .

现在这两件事很难消化

  • 在博客中 setState 函数在函数 updateState 中调用,但触发 updateState 函数的函数不是被调用函数所知道的 .

  • 为什么他们会使 setState async,因为JS是单线程语言,而这个setState不是WebAPI或服务器调用,所以必须只在JS的线程上完成 . 他们是这样做的,以便重新渲染不会停止所有事件监听器和东西,或者存在其他一些设计问题 .

5 回答

  • 107

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

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

    此外,如果您有许多州要立即更新,请将它们全部分组到同一个_1462790中:

    Instead of:
    
    this.setState({foo: "one"}, () => {
        this.setState({bar: "two"});
    });
    
    
    just do this:
    
    this.setState({
        foo: "one",
        bar: "two"
    });
    
  • 4

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

    setState()不会立即改变this.state,但会创建挂起状态转换 . 调用此方法后访问this.state可能会返回现有值 . 无法保证对setState的调用进行同步操作,并且可以对调用进行批处理以获得性能提升 .

    2.)为什么它们会使setState异步,因为JS是单线程语言,而这个setState不是WebAPI或服务器调用:

    这是因为setState改变了状态并导致重新渲染 . 这可能是一项昂贵的操作,并使其同步可能会使浏览器无响应 .
    因此,setState调用是异步的,也可以是批处理的,以获得更好的UI体验和性能 .

  • 59

    我知道这个问题已经过时了,但很长一段时间以来,包括我在内的很多反应用户都引起了很多困惑 . 最近Dan Abramov(来自反应小组)刚刚写了一个很好的解释,为什么 setState 的性质是异步的:

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

    setState 意味着是异步的,在Dan Abramov的链接解释中有一些非常好的理由 . 这并不意味着它将是异步的 - 它主要意味着你不能依赖于 synchronous . ReactJS考虑了您正在更改状态的场景中的许多变量,以确定何时应实际更新 state 并重新呈现组件 .
    一个简单的例子来说明,如果你将 setState 作为对用户操作的反应,那么 state 可能会立即更新(尽管如此,你可以感觉到任何延迟,但如果你对 setState 的反应是一个ajax调用响应或其他一些事件确实没有感觉到这种延迟,它将通过等待批处理多个状态更新并重新渲染DOM更少次来提高性能 .

  • 10

    好文章在这里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

  • 1

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

    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 .

    但是,计数只会增加到1,因为您可以在React Developer工具中进行检查 .

    这证明了反应

    • 将所有setState调用排队

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

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

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

    反应运行的结果代码:

    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调用回调,并传入前一个组件状态 .

    这种方式反应可确保队列中的最后一个回调更新其所有先前的对应物已经完成的状态 .

相关问题