【问题标题】:React's setState method with prevState argumentReact 的 setState 方法和 prevState 参数
【发布时间】:2019-08-24 23:38:38
【问题描述】:

我是 React 新手,只是对 setState 方法有疑问。假设我们有一个组件:

class MyApp extends React.Component {

  state = {
    count: 3
  };

  Increment = () => {
    this.setState((prevState) => ({
      options: prevState.count + 1)
    }));
  }
}

那么为什么我们必须在setState 方法中使用prevState?为什么我们不能这样做:

this.setState(() => ({
  options: this.state.count + 1)
}));

【问题讨论】:

  • 尝试多次调用 setState 命令并亲自查看结果。
  • "React 可能将多个 setState() 调用批处理到单个更新中以提高性能。因为 this.props 和 this.state 可能会异步更新,所以您不应依赖它们的值来计算下一个状态。 "
  • 通常这无关紧要,因为 React 总是会批处理从同一个事件侦听器触发的多个 setStates。也就是说,每当您想设置依赖于旧状态的新状态时,使用回调方法总是一个好主意。
  • @clint_milner 两种方法都不会直接改变状态。两者都很好,一个可以被认为是比另一个“更好的做法”。

标签: javascript reactjs


【解决方案1】:

两种签名都可以使用,唯一的区别是如果您需要根据之前的状态更改您的状态,您应该使用this.setState(function),它将为您提供之前状态的快照(prevState)。但是如果更改不依赖任何其他先前的值,那么建议使用更短的版本this.setState({prop: newValue})

this.setState(prevState =>{
   return{
        ...prevState,
        counter : prevState.counter +1
   }
})

this.setState({counter : 2})

【讨论】:

  • 不要做prevState.counter++,因为这会改变prevState。请改用prevState.counter + 1
  • 确实如此。来自文档The first argument is an updater function with the signature: (state, props) => stateChange. state is a reference to the component state at the time the change is being applied. It should not be directly mutated。此外,当您只想增加后者时,为什么要更新 prevState.counter 和返回对象计数器?理论上,您还可以拥有另一个依赖于prevState.counter 的密钥,如果您使用++,它将无法工作。说,someCountDown: prevState.counter - 1
  • 变量已更新,但我正在生成一个新属性,该属性仅反映先前状态的实际计数器
  • 我只是说这是一种不好的做法。我自己过去曾犯过这个确切的错误并且遇到过问题。
  • 感谢您的建议!
【解决方案2】:
class MyApp extends React.Component {

  state = {
    count: 0
  };

  Increment = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };

  IncrementWithoutPrevState = () => {
    this.setState(() => ({
      count: this.state.count + 1
    }));
    this.setState(() => ({
      count: this.state.count + 1
    }));
    this.setState(() => ({
      count: this.state.count + 1
    }));
    this.setState(() => ({
      count: this.state.count + 1
    }));
  };

  render() {
    return (
      <div>
        <button onClick={this.IncrementWithoutPrevState}>
          Increment 4 times without PrevState
        </button>
        <button onClick={this.Increment}>
          Increment 4 times with PrevState
        </button>
        <h1>Count: {this.state.count}</h1>
      </div>
    );
  }
}

我只是举了一个例子,让你知道“React 可能批处理多个 setState()...”是什么意思,以及为什么我们应该在上述场景中使用 prevState。

首先,尝试猜测当你点击两个按钮时Count的结果应该是什么...如果你认为count会增加4点击两个按钮,这不是正确的猜测;)

为什么?因为IncrementWithoutPrevState 方法中,因为有多个setState 调用,所以React 批处理所有这些调用并仅在此方法中setState 的最后一次调用中更新state,所以在在此方法 this.state.count 中最后一次调用 setState 的时间尚未更新,其值仍与进入 IncrementWithoutPrevState 方法之前的值相同,因此结果状态将包含 count 递增 1。

另一方面,如果我们分析 Increment 方法: 同样有多个 setState 调用,React 将它们全部批处理,这意味着实际状态将在最后一次调用 setState 时更新,但 prevState 将始终包含在最近的 setState 调用中修改后的 state。由于 previousState.count 的值在最后一次调用 setState 之前已经增加了 3 次,因此生成的 state 将包含增加 4 的计数值。

【讨论】:

    【解决方案3】:

    这里每个人都知道 state:prevstate.counter+1.. 我们也可以使用 state:this.state.counter+1 来做到这一点。这不是我认为使用 prevstate 的方式。当我进入现有状态时,我的数组出现问题,它允许我,但第二次它阻止了更改

    【讨论】:

      【解决方案4】:

      如果您在单个render() 函数调用中多次调用一个函数来更改状态属性的值,那么在没有 prevState 机制的情况下,更新的值将不会在不同调用之间传递。

      second(){
         this.setState({ // no previous or latest old state passed 
             sec:this.state.sec + 1
           }, ()=>{
              console.log("Callback value", this.state.sec)
         })    
      }
      
      fiveSecJump(){ // all this 5 call will call together  
             this.second() // this call found sec = 0 then it will update sec = 0 +1
             this.second() // this call found sec = 0 then it will update sec = 0 +1
             this.second() // this call found sec = 0 then it will update sec = 0 +1
             this.second() // this call found sec = 0 then it will update sec = 0 +1
             this.second() // this call found sec = 0 then it will update sec = 0 +1
         }
      
        render() {
              return (
                  <div>
                    <div>  Count - {this.state.sec}</div>
                    <button onClick ={()=>this.fiveSecJump()}>Increment</button>
                  </div>
              )
          }
      

      最后 sec 的值将是 1,因为调用是异步的,不像 c++/c# 或 java 这样的高级编程语言,其中总是有一个 main 函数来维护主线程。
      因此,如果你想让fiveSecJump() 函数正常工作,你必须通过向它传递一个箭头函数来帮助它。上一个状态。 prevState 不是关键字或成员函数,你可以在这里写任何单词,如 oldState、stackTopState、lastState。它将转换为可以完成您所需工作的通用函数。

      class Counter extends Component {
         constructor(props){
              super(props)
              this.state = {
                  sec:0
              }
         }
         second(){
              this.setState(prevState =>({
                  sec:prevState.sec + 1
              }))
         }
      
         fiveSecJump(){
             this.second() // this call found sec = 0 then it will update sec = 0 +1
             this.second() // this call found sec = 1 then it will update sec = 1 +1
             this.second() // this call found sec = 2 then it will update sec = 2 +1
             this.second() // this call found sec = 3 then it will update sec = 3 +1
             this.second() // this call found sec = 4 then it will update sec = 4 +1
      
         }
          render() {
              return (
                  <div>
                    <div>  Count - {this.state.sec}</div>
                    <button onClick ={()=>this.fiveSecJump()}>Increment</button>
                  </div>
              )
          }
      }
      
      export default Counter
      

      【讨论】:

        【解决方案5】:
        //Here is the example to explain both concepts:
        
        
        import React, { Component } from 'react'
        
        export default class Counter extends Component {
            constructor(props) {
                super(props);
                this.state = { counter: 0 }
            }
        
            increment() {
                this.setState({counter:this.state.counter+1})
            }
        
            increment3() {
                this.increment();
                this.increment()
                this.increment()
            }
           render() {
                return (
                    <div>
                       count-{this.state.counter}
                        <div>
                            <button onClick={() => this.increment3()}>Increment</button>
                        </div>
                    </div>
        
                )
            }
        }
        

        在这种情况下,当我们单击 Increment 按钮时,输出将呈现到 UI 中是 count-0 而不是 count-3因为 react 将所有状态调用组合在一个状态调用中​​,并且不会在每个增加的调用中携带更新的值。如果我想根据之前的值更新值,请使用下面提到的代码。

        import React, { Component } from 'react'
        
        export default class Counter extends Component {
            constructor(props) {
                super(props);
                this.state = { counter: 0 }
            }
        
            increment() {
                this.setState((prevState=>({counter:prevState.counter+1})))
            }
        
            incremental() {
                this.increment();
                this.increment()
                this.increment()
             
            }
           render() {
                return (
                    <div>
                       count-{this.state.counter}
                        <div>
                            <button onClick={() => this.incremental()}>Increment</button>
                        </div>
                    </div>
        
                )
            }
        }
        

        在这种情况下,输出将是 count-3 而不是 count-0

        【讨论】:

          【解决方案6】:

          @samee 的回答很棒,帮助了我。我想恢复这个主题来解决 React 函数组件,因为这是我目前正在研究的内容。

          以下示例说明了为什么如果新状态是使用先前状态计算的,您可能希望使用 prevState

          function App() {
            const [developer, setDeveloper] = useState({
              yearsExp: 0,
            });
          
            function handleYearsExp() {
              setDeveloper((prevState) => ({
                yearsExp: prevState.yearsExp + 1, //increments by 1
              }));
              setDeveloper((prevState) => ({
                yearsExp: prevState.yearsExp + 1, //increments by another 1
              }));
            }
          
            return (
              <>
                <button onClick={handleYearsExp}>Increment Years</button> {/* will increment by 2 */}
                <p>
                  I am learning ReactJS and I have {developer.yearsExp} years experience
                </p>
              </>
            );
          }
          

          这是没有传递给setStateprevState 函数的代码。请注意,第一个 setState 函数基本上被忽略了。

          function App() {
            const [developer, setDeveloper] = useState({
              yearsExp: 0,
            });
          
            function handleYearsExp() {
              setDeveloper({
                yearsExp: developer.yearsExp + 1, // is ignored
              });
              setDeveloper({
                yearsExp: developer.yearsExp + 1, //increments by 1
              });
            }
          
            return (
              <>
                <button onClick={handleYearsExp}>Increment Years</button> {/* will only increment by 1 */}
                <p>
                  I am learning ReactJS and I have {developer.yearsExp} years experience
                </p>
              </>
            );
          }
          

          参考资料:

          1. ReactJS.org - "Pass a function to setState"
          2. ReactJS.org - "React may batch multiple setState() calls into a single update for performance.

          【讨论】:

            【解决方案7】:

            我查看了这个问题,也查看了处理事件(react 网站)。我开始想尝试这种方式,对我来说更自然,而且它们奏效了。 当回调中未定义时,我对使用 .bind 感到困惑。我写的handleClick函数是对的吗?

            class Toggle extends React.Component {
              constructor(props) {
                super(props);
                this.state = {isToggleOn: true};
            
                // This binding is necessary to make `this` work in the callback
                // this.handleClick = this.handleClick.bind(this); - i Deleted this because we have arrow fun. down
              
              }
            
              handleClick = (this.setState) => {
                  this.isToggleOn: !this.state.isToggleOn
                }));
              }
            
              render() {
                return (
                  <button onClick={handleClick}>
                    {this.state.isToggleOn ? 'ON' : 'OFF'}
                  </button>
                );
              }
            }
            
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-04-30
              • 2019-05-02
              • 1970-01-01
              • 2018-01-09
              • 1970-01-01
              • 2022-10-08
              • 2020-08-20
              • 1970-01-01
              相关资源
              最近更新 更多