反应JS setState行为

时间:2019-02-17 08:02:00

标签: javascript reactjs

我正在跟踪this tutorial,然后在1:10:18(链接中包含时间步长)上,他写了this.setState({ count: this.state.count + 1 }); (其中count是状态对象中的原始值,然后将其显示在单击按钮后的页面上)从导师的话来说,我了解到我们需要在setState中指定count:,这样React才能在状态对象中更新此值。但是我注意到我可以在setState中写一些废话,例如:

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

并且HTML仍将正确更新,而无需重新呈现所有内容。 (我已经在开发人员工具中签入过,只有已更改的值会更新,其他所有内容都保持不变)

我也尝试过:this.setState({});运作良好。但是this.setState();不会。

那么传递setState参数有什么意义呢?

6 个答案:

答案 0 :(得分:3)

只有可以直接写入this.state的地方应该是Components构造函数。在所有其他地方,您应该使用this.setState函数,该函数将接受一个Object,该Object最终将合并到Components当前状态。

从技术上说,可以通过直接写入this.state来更改状态,但是这不会导致组件使用新数据重新呈现,并且通常会导致状态不一致。

此外,this.setState()是一个异步函数,因此如果在调用新状态后尝试尝试console.log(),则不会立即看到更改。相反,您可以传递回调函数this.setState({var: newVar}, () => {console.log(this.state)})

答案 1 :(得分:2)

react软件包的this行代码之后:

setState方法仅调用enqueueSetState方法。而这又使特定反应组件的更新入队。更新是通过react-dom程序包(或react-native)进行处理

如果您看一下forceUpdate方法,您会发现它实际上在做同样的事情(调用enqueueSetState)。并且此方法仅触发reconciliation进程。

因此,简而言之,不必从纯粹的触发重新呈现前瞻性来看是否必须处于 mutate 状态。但从optimization开始,并且明确的数据流前瞻性非常必要。如果在使用state时依靠setState引用,则当某些组件看到旧状态时,您可能会遇到棘手的行为。

您只需要传递需要更新的值。 React会将当前状态传播到新状态。如果您现在的状态为{ 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 }

并通过调用对帐过程请求更新组件。就是这样,不要忘记:

只要不改变状态:)

P.S。 Here是Dan Abramov(React团队的核心成员)的精彩文章

答案 2 :(得分:1)

通常,您永远不想直接改变状态。始终使用setState

您的问题的答案,为什么我们需要指定密钥(在您的示例中为 count ): 因为这就是我们想要改变的。 setState 不仅会重新渲染组件,而且我们也不会仅出于重新渲染的目的指定键。它会更改状态,然后重新渲染组件,以便可以使用状态中的新值。

答案 3 :(得分:1)

从技术上讲,您可以更改状态并使用随机参数或对象调用forceUpdatesetState,但您不应这样做,因为这会破坏React组件设计的自然工作流程。

如果将对象传递给setState,它会在内部合并它并导致重新渲染,如果没有传递任何内容,它将跳过重新呈现,这是您观察到的行为

现在让我们看看上面代码的缺点

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

在上述情况下,假设您将totalCount和标签作为道具传递给子组件,并基于要进行API调用的totalCount更改或标签更改在子组件中传递。根据React流,您可以在componentDidUpdate生命周期方法中执行此操作

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

但是,尽管上述代码在totalCount为整数,布尔值或字符串的情况下可以工作,因为它们是不可变的,但不适用于对象。这样的情况很难调试,并且可能导致意外的行为。

答案 4 :(得分:1)

您正在做的事情叫做巧合编程。

您的代码只是由于react的内部工作而起作用,但是它是未记录的行为,并且将来可能会在没有警告的情况下停止工作。

建议的方法是使用this.setState({...})并传递对状态所做的所有更改的对象。对于您而言,您可以这样做:

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

这将更新状态并获得与您的代码相同的副作用。

但是,将当前状态视为不变是非常重要的,因为例如,如果将来某个时候您决定实施shouldComponentUpdate方法,并且在状态改变时拒绝状态改变如果是手动突变,则由于之前的状态与您期望的不一致,将会产生意想不到的后果。

答案 5 :(得分:0)

除了其他答案,如果要访问先前的状态,还可以将其作为参数传递给setState。

例如:

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

建议这样做,因为状态可能会异步更新。

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