为什么我不能直接修改组件的状态,真的吗?

2022-08-30 04:50:44

我知道 React 教程和文档毫不含糊地警告说,状态不应该直接改变,一切都应该通过 。setState

我想了解为什么我不能直接更改状态,然后(在同一函数中)调用只是为了触发.this.setState({})render

例如:下面的代码似乎工作得很好:

const React = require('react');

const App = React.createClass({
  getInitialState: function() {
    return {
      some: {
        rather: {
          deeply: {
            embedded: {
              stuff: 1,
            },
          },
        },
      },
    },
  };
  updateCounter: function () {
    this.state.some.rather.deeply.embedded.stuff++;
    this.setState({}); // just to trigger the render ...
  },
  render: function() {
    return (
      <div>
        Counter value: {this.state.some.rather.deeply.embedded.stuff}
        <br></br>
        <button onClick={this.updateCounter}>Increment</button>
      </div>
    );
  },
});

export default App;

我完全赞成遵循约定,但我想进一步了解ReactJS的实际工作原理,以及上面的代码可能会出错或它是否不理想。

this.setState 文档下的注释基本上确定了两个陷阱:

  1. 如果你直接改变状态,然后随后调用它可能会取代(覆盖?)你所做的突变。在上面的代码中,我不明白这是怎么发生的。this.setState
  2. 这可能会以异步/延迟的方式有效地发生变化,因此在调用后立即访问时,您不能保证访问最终的突变状态。我明白了,如果这是更新函数的最后一次调用,这不是问题。setStatethis.statethis.statethis.setStatethis.setState

答案 1

这个答案是提供足够的信息,不直接在 React 中更改/改变状态。

React 遵循单向数据流。这意味着,react 内部的数据流应该并且将被期望处于循环路径中。

无通量的 React 数据流

enter image description here

为了使 React 像这样工作,开发人员将 React 与函数式编程类似。函数式编程的经验法则是不可变性。让我大声而清晰地解释它。

单向流如何工作?

  • states是包含组件数据的数据存储。
  • 组件的 基于状态呈现。view
  • 当需要更改屏幕上的某些内容时,应从 中提供该值。viewstore
  • 为了实现这一点,React 提供了一个函数,该函数接收一个新的,并在以前的状态上进行比较和合并(类似于 ),并将新状态添加到状态数据存储中。setState()objectstatesobject.assign()
  • 每当状态存储中的数据发生变化时,react 将触发使用新状态的重新呈现,并在屏幕上显示它。view

此循环将在整个组件的整个生命周期内持续。

如果您看到上述步骤,它清楚地表明当您更改状态时,后面发生了很多事情。因此,当您直接改变状态并使用空对象调用时。你的突变会污染它们。因此,两个状态的浅层比较和合并将扰或不会发生,因为您现在只有一个状态。这将破坏 React 的所有生命周期方法。setState()previous state

因此,您的应用程序将表现得异常甚至崩溃。大多数情况下,它不会影响您的应用程序,因为我们用于测试它的所有应用程序都非常小。

在JavaScript中,和 突变的另一个缺点是,当你分配一个对象或数组时,你只是在引用那个对象或那个数组。当您改变它们时,对该对象或该数组的所有引用都将受到影响。React 在后台以智能方式处理这个问题,只需给我们一个 API 即可使其正常工作。ObjectsArrays

在 React 中处理状态时最常见的错误

// original state
this.state = {
  a: [1,2,3,4,5]
}

// changing the state in react
// need to add '6' in the array

// bad approach
const b = this.state.a.push(6)
this.setState({
  a: b
}) 

在上面的示例中,将直接改变状态。将其分配给另一个变量并进行调用与下面显示的内容相同。无论如何,当我们改变状态时,将其分配给另一个变量并使用该变量进行调用是没有意义的。this.state.a.push(6)setStatesetState

// same as 
this.state.a.push(6)
this.setState({})

很多人都这样做。这太错误了。这打破了 React 的美感,是糟糕的编程实践。

那么,在 React 中处理状态的最佳方法是什么?让我解释一下。

当您需要更改现有状态中的“某物”时,请首先从当前状态中获取该“某物”的副本。

// original state
this.state = {
  a: [1,2,3,4,5]
}

// changing the state in react
// need to add '6' in the array

// create a copy of this.state.a
// you can use ES6's destructuring or loadash's _.clone()
const currentStateCopy = [...this.state.a]

现在,突变不会改变原始状态。执行操作,并使用 将其设置为新状态。currentStateCopycurrentStateCopysetState()

currentStateCopy.push(6)
this.setState({
  a: currentStateCopy
})

这很美,对吧?

通过这样做,在我们使用 之前,所有的引用都不会受到影响。这使您可以控制代码,这将帮助您编写优雅的测试,并使您对代码在生产中的性能充满信心。this.state.asetState

要回答您的问题,

为什么我不能直接修改组件的状态?


是的,可以。但是,您需要面对以下后果。

  1. 扩展时,将编写难以管理的代码。
  2. 您将失去对跨组件的控制。state
  3. 而不是使用 React,你将在 React 上编写自定义代码。

不可变性不是必需的,因为JavaScript是单线程的。但是,遵循练习是一件好事,从长远来看,这将对你有所帮助。

PS. 我已经写了大约 10000 行可变的 React JS 代码。如果它现在坏了,我不知道在哪里调查,因为所有的值都在某个地方发生了突变。当我意识到这一点时,我开始编写不可变的代码。相信我!这是您可以对产品或应用程序执行此操作的最好的事情。

希望这有帮助!


答案 2

setState 的 React 文档是这样说的:

切勿直接变异,因为事后呼叫可能会取代您所做的突变。像对待它一样对待它是不可变的。this.statesetState()this.state

setState()不会立即发生变异,但会创建挂起的状态转换。调用此方法后访问可能会返回现有值。this.statethis.state

无法保证对 的调用进行同步操作,并且可以对调用进行批处理以提高性能。setState

setState()将始终触发重新渲染,除非在 中实现了条件渲染逻辑。如果正在使用可变对象,并且逻辑无法在 中实现,则仅当新状态与先前状态不同时才调用将避免不必要的重新呈现。shouldComponentUpdate()shouldComponentUpdate()setState()

基本上,如果直接修改,则会创建这些修改可能会被覆盖的情况。this.state

与您的扩展问题1)和2)相关,不是立即的。它根据它认为正在发生的事情对状态转换进行排队,这可能不包括对this.state的直接更改。由于它是排队而不是立即应用的,因此完全有可能在两者之间修改某些内容,以便您的直接更改被覆盖。setState()

如果没有别的,你最好只是考虑不直接修改可以被视为良好的做法。您可能个人知道,您的代码与 React 的交互方式使得这些过度覆盖或其他问题不会发生,但您正在创造一种情况,即其他开发人员或未来的更新可能会突然发现自己遇到奇怪或微妙的问题。this.state