最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why is react setState method immutable? - Stack Overflow

programmeradmin2浏览0评论

Following comes from React tutorial:

const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});

This code changes copied state.squares and assign it to orginal state.squares. Finally this changes original state.squares, so I think this is not different than mutable code like following:

this.state.squares[i] = 'X';

Is there some difference?

Following comes from React tutorial:

const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});

This code changes copied state.squares and assign it to orginal state.squares. Finally this changes original state.squares, so I think this is not different than mutable code like following:

this.state.squares[i] = 'X';

Is there some difference?

Share Improve this question edited Sep 15, 2017 at 6:32 Karol Selak 4,7748 gold badges40 silver badges68 bronze badges asked Sep 15, 2017 at 6:15 TetoteTetote 1211 gold badge1 silver badge5 bronze badges
Add a comment  | 

5 Answers 5

Reset to default 7

I do wonder about this question too. But I find that the answers were unsatisfactory. So here is my take

The answer is actually written in the doc itself http://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly

so first reason, setState() triggers render()

second reason, state changing is asynchronous in React, so you might have some other components changing state behind the scene while you are still referring to the old unchanged state, which result in wrong state values

This code is immutable, because slice() method is used. If you try:

someState = {squares: [1,2,3,4,5]}
squares = someState.squares.slice()

You'll get new array created by slice() method.

You can test it that way:

squares = someState.squares.slice()
squares2 = someState.squares
squares[0] = 9    // doesn't change someState
squares2[1] = 9   // changes someState
someState.squares // [1,9,3,4,5] - as I said

And if you have doubts about this.setState({squares: squares}); - yes, of course after running this you have new state, but in fact this state is not modified old state object, but new object created from old parts. So if you try:

oldState = this.state
this.setState({squares: squares})

You'll see that new state will differ than saved old:

this.state == oldState //false

In case of this.state.squares[i] = 'X'; oldState would be modified too and that is exactly what we call mutability. All copied parts of old state changes with it and that causes many problems.

You can do this, but you should not, the reason behind is that, if you use

this.state.squares[i] = 'X';

It will be overridden with next

this.setState({squares: squares});

So, your app will not have accurate data.

From Doc:

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

Check more about this in https://facebook.github.io/react/docs/react-component.html#state

Do not mutate the state directly, that's what the doc said.

I was coding a todo list and make the same mistake of mutating the state directly, but in my case I was using hooks. I didn't understand why the screen does not re-render when I run setState. The state did mutate (confirmed by console.log) and even useEffect ran because it detects an updated dependency.

React class that extends Purecomponent has the same behaviour too.The funny thing is, if I were to use class that extends React.Component and use this.setState function the app does rerender the screen.

After I ask and learn, it turns out I need to treat states as immutable.

This code : var newwrongArr = this.state.arr; Does not copy anything, it only refers the value, the proof is that if you mutate newwrongArr the state will also mutate.

If we want to copy, the code should be :

var newArr = [...this.state.arr]; //the three dots are spread operator

or some other function like Object.assign().

My conclusion is, if we mutate newwrongArr and setStatearr(newwrongArr), I think React hooks will decide that rerender is unnecessary because it consider newwrongArr to be the same as the value of state (although the state changes, the rerender does not happen). But if we were to copy the value with spread operator, then setState will consider rerender necessary which is the expected result.

Sorry for the long answer.

Agree with @Karol Selak, I want to make thing more clearer with an example here:

const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});

This code will do the following steps:

  • Create a new squares variable that is placed in memory, for example, at 12345
  • Set item at index i to 'X' (at this point, old squares and new squares also got the change because the object in the array is a reference)

When we use, for example the useEffect hook as follows:

useEffect(() => {
   // do something
}[state.squares])

React will compare old squares and new squares, in this case, 2 these are different because the address in memory is different. Then the useEffect will run.

In the second case:

this.state.squares[i] = 'X';

The useEffect will not work

发布评论

评论列表(0)

  1. 暂无评论