简体   繁体   English

反应JS setState行为

[英]React JS setState behaviour

I was following this tutorial , And on 1:10:18 (timestep included in link) he wrote this.setState({ count: this.state.count + 1 }); 我正在学习本教程 ,并在1:10:18(链接中包含时间步长)上,他写了this.setState({ count: this.state.count + 1 }); (where count is a primitive value inside state object, that is then displayed on page after button click) From tutors words i understood that we need to specify count: inside setState so that React will update this value in state object. (其中count是状态对象内的原始值,单击按钮后将显示在页面上)从导师的话中,我了解到我们需要在setState内部指定count:以便React将在状态对象中更新此值。 But i noticed that i can just write some bullshit in setState like: 但是我注意到我可以在setState中写一些废话,例如:

  handleIncrement = key => {
    this.state.totalCount += 1;
    this.state.tags[key] += 1;
    this.setState({ countBlabla: 23213123 });
  };

And HTML still will be updated correctly, without re-rendering everything. 而且HTML仍将正确更新,而无需重新呈现所有内容。 (I've checked in developer tools, only values that were changed are updated, everything else is untouched) (我已经签入开发人员工具,只有已更改的值会更新,其他所有内容都保持不变)

I have also tried: this.setState({}); 我也尝试过: this.setState({}); works good. 效果很好。 But this.setState(); 但是this.setState(); does not. 才不是。

So what's the point of passing setState a parameter? 那么将setState传递给参数有什么意义呢?

Only place you can directly write to this.state should be the Components constructor. 您可以直接写入this.state唯一位置应该是Components构造函数。 In all the other places you should be using this.setState function, which will accept an Object that will be eventually merged into Components current state. 在所有其他地方,您应该使用this.setState函数,该函数将接受一个Object,该Object最终将合并到Components当前状态。

While it is technically possible to alter state by writing to this.state directly, it will not lead to the Component re-rendering with new data, and generally lead to state inconsistency. 尽管在技术上可以通过直接写入this.state来更改状态,但是这不会导致使用新数据重新渲染组件,并且通常会导致状态不一致。

Also, this.setState() is async function so you won't see the changes straight away if you just try to console.log() the new state after it is called. 另外, this.setState()是一个异步函数,因此,如果您只是在调用新状态后尝试console.log()新状态,就不会立即看到更改。 Instead, you can pass a callback function this.setState({var: newVar}, () => {console.log(this.state)}) . 相反,您可以传递一个回调函数this.setState({var: newVar}, () => {console.log(this.state)})

Following this line of code of react package: 遵循以下行react软件包的代码:

setState method just only call the enqueueSetState method. setState方法只调用enqueueSetState方法。 Which in his turn enqueue updating of particular react component. 而这又使特定反应组件的更新入队。 Update is processing by react-dom package (or by react-native ) 更新是通过react-dom包(或react-native )处理的

If you will take a look on forceUpdate method, you will see that its doing actually the same thing (calling enqueueSetState ). 如果您看一下forceUpdate方法,您会发现它实际上在做同样的事情(调用enqueueSetState )。 And this method is only triggering reconciliation process. 而且此方法仅触发对帐过程。

So in short it is not necessary mutate state or not from the pure triggering rerender prospective. 因此,简而言之,没有必要从纯触发前瞻性的状态中改变状态,也可以不改变状态。 But it is highly necessary from the optimization and clear data-flow prospective. 但是从优化和清晰的数据流角度出发,这是非常必要的。 If you will rely on the state reference when using setState you can meet tricky behavior when some components will see old state. 如果在使用setState时依赖state引用,则当某些组件看到旧状态时,您可能会遇到棘手的行为。

You need to pass only that values that needs to be updated. 您只需要传递需要更新的值。 React will make spread of current state into new one. React会将当前状态传播到新状态。 If your state now is { count: 1, prop: 2 } : 如果您现在的状态为{ count: 1, prop: 2 }

// if you will make 
this.setState({ count: 11 })

// react will do
{ ...currentState, count: 11 }

// and result will be
{ prop: 2, count: 11 }

And enque updating of component by calling reconciliation process. 并通过调用对帐过程请求更新组件。 That's it and don't forget to: 就是这样,不要忘记:

Just don't mutate state :) 只是不要改变状态:)

PS Here is nice article by Dan Abramov (core member of react team) PS 是Dan Abramov(React团队的核心成员)的精彩文章

In general, you never want to directly mutate the state. 通常,您永远不想直接改变状态。 Always use setState . 始终使用setState

The answer to your question, why do we need to specify the key ( count in your example): Because that's what we want to change. 在回答你的问题,为什么我们需要指定键(在您的示例 ):因为这是我们要改变。 setState doesn't just re-render the component and we don't specify a key just for the purpose of re-rendering. setState不仅会重新渲染组件,而且我们也不只是为了重新渲染而指定键。 It changes state and then re-renders the component so it could use the new values in state. 它更改状态,然后重新渲染组件,以便可以使用状态中的新值。

While technically you can mutate state and call either forceUpdate or setState with random parameters or an object, you shouldn't do that because that breaks the natural workflow of React component design. 从技术上讲,您可以forceUpdate状态并使用随机参数或对象调用forceUpdatesetState ,但您不应这样做,因为这会破坏React组件设计的自然工作流程。

If you pass an object to setState, it internally merges it and causes a re-render and if nothing is passed it skips the re-render which is the behaviour that you observe 如果将对象传递给setState,它将在内部合并该对象并导致重新渲染;如果未传递任何内容,它将跳过该重新渲染,这是您观察到的行为

Now lets look at the downside of the above code 现在让我们看一下上面代码的缺点

handleIncrement = key => {
    this.state.totalCount += 1;
    this.state.tags[key] += 1;
    this.setState({ countBlabla: 23213123 });
  };

In the above case, let suppose you pass totalCount and tags as props to the child component and in the child component based on totalCount change or tags change you want to make an API call. 在上述情况下,假设您将totalCount和标签作为道具传递给子组件,并基于要进行API调用的totalCount更改或标签更改在子组件中传递。 According to React flow, you would do that in componentDidUpdate lifecycle method 根据React流,您可以在componentDidUpdate生命周期方法中执行此操作

componentDidUpdate(prevProps) {
    if (prevProps.totalCount !== this.props.totalCount) {
        // make api cal;
    }
}

However, although the above code will work if totalCount is interger, boolean or string since they are immutable, it won't work with an object. 但是,尽管以上代码在totalCount为整数,布尔值或字符串的情况下都可以使用,因为它们是不可变的,但不适用于对象。 such cases will be difficult to debug and might lead to unexpected behaviours. 这样的情况将很难调试,并可能导致意外的行为。

What you are doing is called programming by coincidence. 您正在做的事情叫做巧合编程。

Your code just happens to work due to the internal workings of react it is however undocumented behaviour and might stop working in the future without warning. 您的代码恰好由于react的内部工作而起作用,但是它是未记录的行为,将来可能会在没有警告的情况下停止工作。

The suggested way is to use this.setState({...}) and pass an object of all changes made to the state. 建议的方法是使用this.setState({...})并传递对状态所做的所有更改的对象。 In your case you would do: 您的情况是:

  this.setState({ 
      totalCount: this.state.totalCount + 1 
      tags: {
           ...this.state.tags,
          [key]: this.state.tags[key] + 1 
       }  
  });

This will update the state and achieve the same side-effects as your code. 这将更新状态并获得与您的代码相同的副作用。

However it is important to treat the current state as immutable because for example if at some point in the future you decide to implement a shouldComponentUpdate method and reject a state change if the state was mutated manually you will have some unintended consequences due to an inconsistent previous state to what you'd expect. 但是 ,将当前状态视为不可变是很重要的,因为例如,如果将来某个时候您决定实施shouldComponentUpdate方法并拒绝状态更改(如果手动更改了状态),由于先前的不一致,将会产生一些意想不到的后果陈述您的期望。

In addition to other answers, if you are going to access the previous state, you can also pass it as a param to the setState. 除其他答案外,如果要访问先前的状态,还可以将其作为参数传递给setState。

Eg: 例如:

 this.setState((prevState) => ({
      totalCount: prevState.totalCount + 1 
      tags: {
           ...prevState.tags,
          [key]: prevState.tags[key] + 1 
       }  
  }));

This is recommended as this.state may be updated asynchronously. 建议这样做,因为此状态可能会异步更新。

More info: https://reactjs.org/docs/state-and-lifecycle.html 更多信息: https//reactjs.org/docs/state-and-lifecycle.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM