【问题标题】:Is there a synchronous alternative of setState() in ReactjsReactjs 中是否有 setState() 的同步替代方案
【发布时间】:2017-06-20 11:25:52
【问题描述】:

根据docs中的解释:

setState() 不会立即改变 this.state 而是创建一个挂起的状态转换。调用此方法后访问 this.state 可能会返回现有值。

不保证 setState 调用的同步操作,并且调用可能会被批处理以提高性能。

因此,由于setState() 是异步的,因此无法保证其同步性能。有没有同步的setState() 的替代品。

例如

//initial value of cnt:0
this.setState({cnt:this.state.cnt+1})
alert(this.state.cnt);    //alert value:0

由于alert 值是以前的值,那么使用setState() 提供alert value:1 的替代方案是什么。

Stackoverflow 上与此问题类似的问题很少,但我无法找到正确答案。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    正如您从文档中读到的,没有同步替代方案,所描述的原因是性能提升。

    但是我认为您想在更改状态后执行操作,您可以通过以下方式实现:

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
         x: 1
        };
        
        console.log('initial state', this.state);
      }
      
      updateState = () => {
       console.log('changing state');
        this.setState({
          x: 2
        },() => { console.log('new state', this.state); })
      }
      
      render() {
        return (
          <div>
          <button onClick={this.updateState}>Change state</button>
        </div>
        );
       
      }
    }
    
    ReactDOM.render(
      <MyComponent />,
      document.getElementById("react")
    );
    <div id="react"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

    【讨论】:

    • 你能举个例子吗
    【解决方案2】:

    您可以将setState 包装在一个返回promise 的函数中,然后将此函数与await 关键字一起使用,以使您的代码等待状态被应用。

    就我个人而言,我永远不会在实际代码中执行此操作,而是将我希望在状态更新后执行的代码放在 setState 回调中。

    神经兮兮的,这是一个例子。

    class MyComponent extends React.Component {
    
        function setStateSynchronous(stateUpdate) {
            return new Promise(resolve => {
                this.setState(stateUpdate, () => resolve());
            });
        }
    
        async function foo() {
            // state.count has value of 0
            await setStateSynchronous(state => ({count: state.count+1}));
            // execution will only resume here once state has been applied
            console.log(this.state.count);  // output will be 1
        }
    } 
    

    在 foo 函数中,await 关键字导致代码执行暂停,直到 setStateSynchronous 返回的 promise 被解决,这仅在调用传递给 setState 的回调时发生,仅当状态已应用。因此,只有在应用状态更新后才会执行 console.log 调用。

    异步/等待文档:
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await

    【讨论】:

      【解决方案3】:

      如果需要,我建议在您的 setState 函数中使用回调(我还建议使用函数式 setState)。

      状态更新后会调用回调。

      例如,您的示例将是

      //initial value of cnt:0
      this.setState(
          (state) => ({cnt: state.cnt+1}),
          () => { alert(this.state.cnt)}
      )
      

      根据此处的文档:https://facebook.github.io/react/docs/react-component.html#setstate

      注意:官方文档说,“一般我们建议使用 componentDidUpdate() 来代替这种逻辑。”

      【讨论】:

        【解决方案4】:

        不,没有。 React 会在它认为合适的时候更新状态,做一些事情比如批处理 setState 调用以提高效率。您可能会感兴趣的是,您可以将一个函数传递给setState,它采用以前的状态,因此您可以在熟悉前一个状态的情况下选择您的新状态。

        【讨论】:

          【解决方案5】:

          这听起来可能很奇怪,但是 setState 可以在 react 中同步工作。 为何如此?这是我为演示而创建的 POC。

          粘贴唯一的app JS代码。

          也许我遗漏了一些东西,但这实际上发生在我的应用程序中,那时我才知道这种效果。

          如果我不知道在 React 中会出现这种行为,请纠正我。 当主线程上有多个 setState 时, setState 运行一个 Batch 组合主方法上的所有 setState。而当相同的东西进入异步函数时,场景是不同的。

          import React, { Component } from 'react';
          import './App.css';
          
          class App extends Component {
            constructor(props) {
              super(props);
              this.state = {
                counter: 0
              }
              this.asyncMethod = this.asyncMethod.bind(this);
              this.syncMethod = this.syncMethod.bind(this);
            }
          
            asyncMethod() {
              console.log("*************************")
              console.log("This is a async Method ..!!")
              this.setState({
                counter: this.state.counter + 1
              }, () => {
                console.log("This is a async Method callback of setState. value of counter is---", this.state.counter);
              })
              console.log("This is a async Method on main thread. value of counter is---", this.state.counter);
              console.log("*************************")
            }
          
            syncMethod() {
              var that = this;
              console.log("*************************")
              console.log("This is a sync Method ..!!")
              that.setState({counter: "This value will never be seen or printed and render will not be called"});
              that.setState({counter: "This is the value which will be seen in render and render will be called"});
              setTimeout(() => {
                that.setState({counter: "This is part is synchronous. Inside the async function after this render will be called"});
                console.log("setTimeout setState");
                that.setState({counter: "This is part is aslso synchronous. Inside the async function after this render will be called"});
              }, 10)
              console.log("This is a sync Method on Main thread. value of counter is---", this.state.counter);
              console.log("*************************")
            }
          
            render() {
              console.log("Render..!!",this.state.counter);
              return (
                <div className="App">
                  <header className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <p>
                      Edit <code>src/App.js</code> and save to reload.
                    </p>
                  </header>
                    <button onClick={this.asyncMethod}>AsyncMethod</button>
                    <button onClick={this.syncMethod}>SyncMethod</button>
                </div>
              );
            }
          }
          
          export default App;
          

          【讨论】:

          • 欢迎来到stackoverflow!很高兴看到您花时间回答这个问题。但我必须指出,在文档中(以及问题描述中)它说“不保证同步”,这意味着您可能正在经历同步状态集,但您不应该依赖它。因此,您提到的行为对问题没有帮助。
          【解决方案6】:

          改用 React Hooks:

          function MyComponent() {
            const [cnt, setCnt] = useState(0)
          
            const updateState = () => {
              setCnt(cnt + 1)
            }
          
            useEffect(() => {
              console.log('new state', cnt)
            }, [cnt])
          
            return (
              <div>
                <button onClick={updateState}>Change state</button>
              </div>
            )
          }
          

          【讨论】:

            【解决方案7】:

            通过将我的代码包装在 setTimeout(() =&gt; {......this.setState({ ... });....}, 0); 中,我能够欺骗 React 同步调用 setState。由于 setTimeout 将内容放在 JavaScript 事件队列的末尾,我认为 React 检测到 setState 在其中并且知道它不能依赖批处理的 setState 调用(它将被添加到队列的末尾) .

            【讨论】:

              【解决方案8】:

              是的,有一种方法可以使我们同步设置状态。但它的性能可能不如平时好 例如,我们有

              class MyComponent extends React.Component {
                constructor(props) {
                  super(props);
                  this.state = {
                   data: 0
                  };
                }
              
                changeState(){
                 console.log('in change state',this.state.data);
                 this.state.data = 'new value here'
                 this.setState({});
                 console.log('in change state after state change',this.state.data);
                }
              
                render() {
                  return (
                    <div>
                    <p>{this.state.data}</p>
                    <a onClick={this.changeState}>Change state</a>
                  </div>
                  );
              
                }
              }  
              

              在这个例子中,我们先改变状态,然后渲染我们的组件。

              【讨论】:

              • 你不应该像这样直接改变状态。见the docs
              • @ScottSilvi 是的,我知道这就是为什么我说“性能可能不如平常好”。如果你想申请,我只是举了一个例子。
              • @PulkitAggarwal 除了性能之外,是不是有机会手动设置状态导致状态不一致?
              • 直接改变状态是不好的做法。而是在 setState 中使用回调函数。
              【解决方案9】:

              对您的问题的简短回答是 - 不,react 没有同步方法 setState

              【讨论】:

                【解决方案10】:

                在功能组件中我这样做:

                const handleUpdateCountry(newCountry) {
                    setIsFetching(() => true);
                    setCompanyLocation(() => newCountry);
                    setIsFetching(() => false);
                }
                

                如果我错了,请纠正我,但据我所知,这是同步的,它也适用于我的情况。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-07-23
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-09-02
                  • 1970-01-01
                  • 2017-03-21
                  • 1970-01-01
                  相关资源
                  最近更新 更多