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

时间:2016-03-18 13:30:44

标签: javascript multithreading asynchronous reactjs

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

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

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

现在这两件事很难消化

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

8 个答案:

答案 0 :(得分:125)

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

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

此外,如果您要同时更新多个州,请将它们全部归入同一个setState

而不是:

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

这样做:

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

答案 1 :(得分:67)

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

  

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


2.)为什么他们将setState async作为JS是单线程语言而且这个setState不是WebAPI或服务器调用:

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

答案 2 :(得分:13)

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

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

setState意味着是异步的,在Dan Abramov的链接解释中有一些非常好的理由。这并不意味着始终是异步的 - 这主要意味着您依赖 同步。 ReactJS会考虑您正在更改状态的方案中的许多变量,以确定何时应实际更新state并重新呈现组件。
一个简单的例子来说明,如果你将setState作为对用户操作的反应,那么state可能会立即更新(尽管你再也不能指望它) ,因此用户不会感到任何延迟,但是如果您对ajax呼叫响应或其他未被用户触发的事件进行调用setState,则状态可能会稍有延迟更新,因为用户不会真正感受到这种延迟,并且它会通过等待批处理多个状态更新并将DOM重新渲染更少次来提高性能。

答案 3 :(得分:6)

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

答案 4 :(得分: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()调用而重新呈现(这是一个非常原始的批处理描述)。

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

这种方式反应可以确保队列中的最后一个回调更新所有以前的对手都已经掌握的状态。

答案 5 :(得分:0)

您可以使用以下包装来进行同步通话

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

答案 6 :(得分:0)

是的,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 :(得分:0)

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
    }
}