我知道this.state不应该直接修改,而应该使用setState

据此我推断prevState也应被视为不可变的,而setState应该始终看起来像这样:

this.setState((prevState) => {
  // Create a new object instance to return
  const state = { ...prevState };
  state.counter = state.counter + 1;
  return state;
});

或更深层的嵌套:
this.setState((prevState) => {
  // Create a new object instance to return
  const state = { ...prevState, counter: { ...prevState.counter } };
  state.counter.value = state.counter.value + 1;
  return state;
});

或只是像setState({})这样的部分更新,在其中更容易使用。
this.setState((prevState) => ({ counter: prevState.counter + 1 }));

上面所有这些显然都是正确的,因为它们返回了一个新实例,但是随后我遇到了this question where the accepted answer encourages mutating prevState without returning a new object instance(注意问题中的代码块)。

像这样的东西:
this.setState((prevState) => {
  prevState.flag = !prevState.flag;
  return prevState;
});

我发现这是一个粗略的建议,因此我决定测试prevStatethis.state的对象实例引用是否相同:

(The JSFiddle)
class Test extends React.Component {
  state = { counter: 0 };

  onDemonstrateButtonClick = (event) => {
    this.setState((prevState) => {
      if (prevState === this.state) alert(`uh, yep`);
      prevState.counter++;
      return prevState;
    });
  };

  render() {
    return (
      <div>
        <button onClick={this.onDemonstrateButtonClick}>Demonstrate</button>
        {this.state.counter}
      </div>
    );
  }
}

Whadayaknow,他们是!那是什么呢?答案是否正确,我应该返回一个新的对象实例还是将部分更新作为一个新对象返回,还是可以疯狂地直接更改prevState参数?如果是的话,这与直接更改this.state有何不同?

旁注:TypeScript React输入不会将参数标记为ReadOnly,这只会增加我的困惑。

最佳答案

第一点



答案是,您不应该突变prevState,在react documentation in setState section中也明确提到


  • 第二点:

    您测试了prevStatethis.state,它们是相同的,实际上它们不是相同的。

    为了弄清楚为什么它们实际上不同,我们需要知道为什么prevState实际上存在,答案是setState函数是异步的,这就是为什么react js允许我们访问prevState的原因,请检查下面的示例,其中prevState != this.state
    在下面的示例中,每次单击将使计数器增加两次,但是我们将使用2个setState操作,其中每个操作都会使计数器增加1。

    由于setStateasync,因此您会注意到第二个setState操作在第一个setState完成之前开始,这是prevState有用的地方,此处prevStatethis.state不相等。

    我在每行注释了一个数字,表示执行该行的时间,这应该解释为什么我们需要prevState以及为什么它与this.state不同。
    class App extends React.Component{
      constructor(props)
      {
        super(props);
        this.state = {
          counter : 1
        };
      }
    
      increment = () =>{
        this.setState((prevState , props) => {
          console.log("in first"); //3
          console.log(prevState);  //3
          console.log(this.state); //3
          if(prevState == this.state)
          {
             console.log("in first prevState == this.state");
          }
          return {
            counter : prevState.counter+1
          }
        } , ()=>{
          console.log("done updating first"); //5
        });
    
    
        console.log("after first"); //1
    
        this.setState((prevState, props) => {
          console.log("in second"); //4
          console.log(this.state);  //4
          console.log(prevState);   //4
          if (prevState == this.state) {
            console.log("in second prevState == this.state");
          }
          return {
            counter: prevState.counter + 1
          }
        } , () =>{
          console.log("done updating second"); //6
        });
    
        console.log("after second"); //2
    
      }
    
      render(){
        return (
          <div>
            <span>{this.state.counter}</span>
            <br/>
            <button onClick={this.increment} >increment</button>
          </div>
        )
      }
    }
    

    上面代码的结果是
    "after first"
    "after second"
    "in first"
    ▶Object {counter: 1}
    ▶Object {counter: 1}
    "in first prevState == this.state"
    "in second"
    ▶Object {counter: 1}
    ▶Object {counter: 2}
    "done updating first"
    "done updating second"
    

    上面的代码在此链接中完全可用,您可以检查console.log结果
    https://codesandbox.io/s/k325l485mr

    上面的示例将正确地使计数器每次点击递增两次,如果您想使其中断,请在第二个setState中更改change语句


    return {
        counter: prevState.counter + 1
    }
    


    return {
        counter: this.state.counter + 1
    }
    

    并且您会发现结果不正确,因为每次单击都会导致1的增量不正确,因为我们有2个setState,这是因为我们没有使用prevState,并且使用了错误的this.state
    最后

    我认为更新计数器的正确方法是
    this.setState((prevState) => ({ counter: prevState.counter + 1 }));

    关于javascript - 是否可以将setState函数的prevState参数视为可变的?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47339643/

    10-10 19:07