是否可以将setState函数的prevState参数视为可变? [英] Is it okay to treat the prevState argument of setState's function as mutable?

查看:81
本文介绍了是否可以将setState函数的prevState参数视为可变?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I know this.state is not supposed to be modified directly, instead setState should be used.

从此我推断 prevState 也应该被视为不可变的,而不是 setState 应该总是这样:

From this I inferred that prevState should be also treated as immutable and instead setState should always look something like this:

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({})更容易和更好使用:

Or just a partial update like would be with setState({}) where easier and nicer to use:

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

所有上述内容显然是正确的,因为它们返回一个新实例,但后来我遇到了这个问题,接受的答案鼓励变异 prevState 而不返回新的对象实例 (注意问题中的代码块)。

All of the above are obviously correct because they return a new instance, but then I came across this question where the accepted answer encourages mutating prevState without returning a new object instance (notice the code block in the question).

这样的事情:

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

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

I found this to be a sketchy suggestion so I decided to test if the object instance references of prevState and this.state are the same:

(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 直接改变有何不同?

Whadayaknow, they are! So which is it? Is the answer wrong and should I return a new object instance or return partial update as a new object or can I go wild and mutate the prevState argument directly? And if yes, how is this any different from mutating this.state directly?

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

Side note: TypeScript React typings do not mark the argument as ReadOnly which only adds to my confusion.

推荐答案

第一点



First Point


是否可以将setState函数的prevState参数视为
mutable ?

Is it okay to treat the prevState argument of setState's function as mutable?

答案是你永远不应该改变 prevState ,这反映在 setState部分中的文档中也有明确提及

The answer is NO you should never mutate prevState, this is also clearly mentioned in react documentation in setState section



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

prevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from prevState and props.


您测试了 prevState this.state 并且它们是相同的,实际上它们不是。

You tested prevState and this.state and they are the same, well actually they are not.

要弄清楚它们实际上是不同的原因我们需要知道为什么 prevState 实际存在,答案是 setState 函数是异步的,这就是为什么反应js让我们访问 prevState 让我们查看下面的示例 prevState!= this.state

To figure out why they are actually different we need to know why prevState actually exist, and the answer is that setState function is asynchronous, thats why react js is giving us access to prevState lets check the example below where prevState != this.state

在下面的示例中,我们将每次点击增加两次计数器,但我们将使用2 setState 操作,每个操作都会将计数器增加1。

In the example below we will increment counter twice per click, but we will use 2 setState operations each one of them will increment the counter by 1.

因为 setState async 你会注意到第二个 setState 操作在第一个 setSt之前开始ate 已经完成,这是 prevState 非常有用,这里 prevState this.state 不相等。

Because setState is async you will notice that the second setState operation started before the first setState is finished this is where prevState is useful and here prevState and this.state are not equal.

我用一个数字表示每一行表示执行此行的时间,这应该解释为什么我们这样做需要 prevState 以及为什么它与 this.state 不同。

I commented each line with a number denoting when this line is executed, this should explain why we need prevState and why it is different from 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

The above code is fully working in this link, you can check the console.log result https://codesandbox.io/s/k325l485mr

以上示例将正确递增计数器两次每次点击,如果你想在第二个 setState

The above example will correctly increment counter twice per click, if you want to break it change return statement in second setState

中打破它更改退货声明

return {
    counter: prevState.counter + 1
}

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

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

and you will find that the result is not correct each click will result in 1 increment which is not correct because we have 2 setState , this is because we didn't use prevState and we used an incorrect this.state

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

I believe that the correct way to update the counter is

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

这篇关于是否可以将setState函数的prevState参数视为可变?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆