小编典典

可以将setState函数的prevState参数视为可变的吗?

reactjs

我知道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 }));

上面所有这些显然都是正确的,因为它们返回了一个新实例,但是随后我遇到了这个问题,prevState在该问题中,可接受的答案鼓励在不返回新对象实例的情况下进行变异(注意问题中的代码块)。

像这样:

this.setState((prevState) => {
  prevState.flag = !prevState.flag;
  return prevState;
});

我发现这是一个粗略的建议,所以我决定测试一下的对象实例的引用prevState,并this.state是相同的:

(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这只会增加我的困惑。


阅读 298

收藏
2020-07-22

共1个答案

小编典典

第一点

可以将setState函数的prevState参数视为可变的吗?

答案是“ 不”,
您永远都不要突变prevState,这在setState部分的 react 文档中也有明确提及

  • prevState是对先前状态的引用。它不应该直接突变。相反,更改应通过根据prevState和props的输入构建一个新对象来表示。

第二点:

经过测试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


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

return {
    counter: prevState.counter + 1
}

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

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

最后

我相信更新计数器的正确方法是

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

2020-07-22