为什么reactjs中的setState是Async而不是Sync?


126

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

现在我搜索并找到了这个博客(setState()状态更改操作在ReactJS中可能是同步的

在这里,他发现setState是async(在堆栈为空时调用)或sync(在调用后立即调用),具体取决于触发状态更改的方式。

现在这两件事很难消化

  1. 在博客中,该setState函数在一个函数内部被调用updateState,但是触发该updateState函数的并不是被调用函数知道的东西。
  2. 他们为什么要使setState异步,因为JS是单线程语言,而此setState不是WebAPI或服务器调用,因此只能在JS的线程上完成。他们这样做是为了使Re-Rendering不会停止所有事件侦听器和其他操作,或者存在其他设计问题。


1
我今天帮助周围形容一点气候的写了一篇文章setStatemedium.com/@agm1984/...
agm1984

Answers:


155

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

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

另外,如果您要一次更新许多状态,请将它们全部分组在同一状态下setState

代替:

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

只是这样做:

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

16
是的,好的,我们有一个可以使用的callBack函数,但不是问题。
Anup

12
希望它将帮助其他人在这个问题上跌跌撞撞。
JoeTidee '16

2
ya dat可能会有所帮助
Anup

97

1)setState操作是异步的,并且为提高性能而进行了批处理。的文档中对此进行了说明setState

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


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

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

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


59
如果需要确保在进行setState调用后事件的顺序,则可以传递一个回调函数。 this.setState({ something: true }, () => console.log(this.state))
ianks

1
谢谢@Sachin的解释。但是,我仍然有疑问,是否可以按照博客的解释是同步的?
阿杰·高尔

2
反应中的另一个愚蠢的设计决定。使状态更新同步,并使渲染异步。您可以批处理渲染,但我希望能够将某些内容设置为原始状态变量,而不必处理竞争条件。
ig-dev

为什么不允许设置选项来使功能异步或同步呢?那将是一个有用的功能
Randall Coding

显然,我不在反应团队中,但是我认为使状态更新异步的一个原因是浏览器是单线程的。同步操作会使UI无响应,因此不适合使用UI。
萨钦

16

我知道这个问题很老,但是很长一段时间以来,它一直在引起很多Reactjs用户的困惑,包括我在内。最近,来自React小组的Dan Abramov撰写了一个很好的解释,说明为什么setStateasync 的本质是异步的:

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

setState它是异步的,在Dan Abramov的链接说明中确实有很好的理由。这并不意味着它永远都是异步的-它主要意味着您不能仅仅依靠它是同步的。在更改状态的情况下,ReactJS考虑了许多变量,以确定何时state应该实际更新和重新渲染组件。
一个简单的示例来说明这一点,即如果您setState作为对用户操作的反应来调用,则state可能会立即进行更新(尽管再次,您不能指望它),因此用户不会感到任何延迟,但如果您致电setState 为了响应ajax呼叫响应或用户未触发的其他事件,状态可能会稍有延迟进行更新,因为用户不会真正感觉到这种延迟,并且通过等待一起批处理多个状态更新,并减少重新呈现DOM的次数。


您尚未将任何答案标记为正确的答案。人们正在发布如何解决它。不是所问问题的答案。这篇文章看起来不错。
Anup

@Anup答案比“异步”或“同步”要复杂得多。应始终将其视为“异步”,但在某些情况下可能会充当“同步”。希望我能为您介绍一些信息。
gillyb

8

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

父组件和子组件都有一个计数处理程序。这样做是有目的的,因此我们可以在相同的click事件冒泡上下文中执行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调用回调,并传入先前的组件状态。

这种反应方式可确保队列中的最后一个回调能够更新其所有先前对应对象已动手的状态。


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) {
    // ..
}

希望有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.