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

31 浏览
0 Comments

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

我知道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({}); // 仅用于触发render方法...
  },
  render: function() {
    return (
        Counter value: {this.state.some.rather.deeply.embedded.stuff}
        
); }, }); export default App;

我支持遵循惯例,但我希望进一步了解ReactJS的工作原理,以及上述代码可能出现什么问题或者是否不够优化。在this.setState文档下的注释基本上指出了两个问题:

  1. 如果你直接改变状态然后随后调用this.setState,这可能会替换(覆盖?)你所做的改变。我不明白这在上面的代码中如何发生。
  2. setState可能以异步/延迟的方式改变this.state,因此在调用this.setState之后立即访问this.state时,无法保证访问到最终改变的状态。我理解这一点,但如果this.setState是更新函数的最后一次调用,这不是一个问题。
0
0 Comments

更新组件状态时,React会重新渲染整个组件树。但重新渲染整个组件树并不意味着整个DOM都会被更新。实际上,当组件被渲染时,我们得到的是一个React元素,这个元素会更新我们的虚拟DOM。React会对比新旧虚拟DOM,因此我们不能直接更新状态。这样,我们就会在内存中有两个不同的对象引用,一个是旧的虚拟DOM,一个是新的虚拟DOM。然后React会找出有变化的部分,并相应地更新真实DOM。希望这能帮到你。

解决方法是使用setState方法来更新组件的状态。下面是一个示例代码:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }
  render() {
    return (
      

Count: {this.state.count}

); } }

0
0 Comments

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

React文档中的setState部分有这样的说法:

绝对不要直接修改this.state,因为随后调用setState()可能会替换你所做的修改。把this.state看作是不可变的。

setState()不会立即修改this.state,而是创建一个待处理的状态转换。在调用此方法后访问this.state可能会返回现有的值。

调用setState()并没有保证同步操作,调用可能会批量处理以提高性能。

setState()总是会触发重新渲染,除非在shouldComponentUpdate()中实现了条件渲染逻辑。如果使用了可变对象,并且无法在shouldComponentUpdate()中实现逻辑,只有在新状态与之前状态不同时才调用setState(),以避免不必要的重新渲染。

基本上,如果直接修改this.state,会导致这些修改可能被覆盖。

与你的问题1)和2)相关,setState()不是立即生效的。它会根据它认为正在发生的情况排队进行状态转换,这可能不包括对this.state的直接更改。由于它是排队而不是立即应用的,完全有可能在之间进行修改,以致于您的直接更改被覆盖。

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

“它会根据它认为正在发生的情况排队进行状态转换,这可能不包括对this.state的直接更改。由于它是排队而不是立即应用的,完全有可能在之间进行修改,以致于您的直接更改被覆盖。”请详细说明您所说的是什么意思?给出一个在此情况下出现问题的例子?

深入研究React代码库:处理状态更改这篇文章详细介绍了setState()和回调系统的工作原理,让你对为什么会出现问题有一个很好的理解。

谢谢Ouroborus的提示!赞了这个回答:“它会根据它认为正在发生的情况排队进行状态转换,这可能不包括对this.state的直接更改”。

0
0 Comments

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

React遵循单向数据流的原则,即React内部的数据流应该是一个循环路径。为了使React能够按照这种方式工作,开发人员使React类似于函数式编程,而函数式编程的基本原则是不可变性。

在React中,状态是组件的数据存储区,视图根据状态进行渲染。当视图需要在屏幕上改变某些内容时,该值应从存储区中提供。为了实现这一点,React提供了setState()函数,它接受一个新状态的对象,并对先前的状态进行比较和合并,然后将新状态添加到状态数据存储区中。每当状态存储区中的数据发生变化时,React都会以新状态触发重新渲染,视图会消费并显示在屏幕上。这个循环将在组件的整个生命周期中继续进行。

根据上面的步骤,可以清楚地看到,当您改变状态时发生了很多事情。因此,当您直接修改状态并使用空对象调用setState()时,先前的状态将被污染。由于您现在只有一个状态,两个状态的浅比较和合并将被破坏或不会发生,这将干扰所有的React生命周期方法。结果是,您的应用程序将表现异常甚至崩溃。大多数情况下,它不会影响您的应用程序,因为我们用于测试的所有应用程序都非常小。

另外,直接修改JavaScript中的对象和数组的一个缺点是,当您分配对象或数组时,实际上只是创建了对该对象或数组的引用。当您对它们进行修改时,所有对该对象或数组的引用都将受到影响。React在后台以智能的方式处理这个问题,并为我们提供了一个API来使其正常工作。

在处理React状态时,最常见的错误有:

// 原始状态
this.state = {
  a: [1,2,3,4,5]
}
// 在React中修改状态
// 需要在数组中添加'6'
// 错误的做法
const b = this.state.a.push(6)
this.setState({
  a: b
}) 

在上面的示例中,this.state.a.push(6)会直接修改状态。将其赋值给另一个变量并调用setState与下面所示的代码相同。由于我们无论如何都修改了状态,将其赋值给另一个变量并调用带有该变量的setState是没有意义的。

// 等同于 
this.state.a.push(6)
this.setState({})

许多人都这样做,这是完全错误的。这破坏了React的美感,是一种糟糕的编程实践。

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

当您需要改变现有状态中的某个部分时,首先从当前状态中获取该部分的副本。

// 原始状态
this.state = {
  a: [1,2,3,4,5]
}
// 在React中修改状态
// 需要在数组中添加'6'
// 创建this.state.a的副本
// 您可以使用ES6的解构赋值或lodash的_.clone()函数
const currentStateCopy = [...this.state.a]

现在,对currentStateCopy进行修改不会修改原始状态。对currentStateCopy进行操作,并使用setState将其设置为新状态。

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

这很美妙,对吧?

通过这样做,只有在使用setState之前,所有对this.state.a的引用才不会受到影响。这使您可以控制代码,并且可以编写优雅的测试,并且可以对代码在生产环境中的性能感到自信。

回答您的问题,

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

实际上,您是可以的。但是,您需要面对以下后果:

- 当您扩展规模时,您将编写无法管理的代码。

- 您将失去对组件之间状态的控制。

- 您将不再使用React,而是在React上编写自定义代码。

不可变性不是JavaScript的必需性,因为JavaScript是单线程的,但遵循能够帮助您的长期实践是一个好主意。

提醒一下:JavaScript中的大多数基本克隆方法(如slice、ES6的解构赋值等)都是浅克隆。如果您有嵌套的数组或嵌套的对象,您需要查看其他深层复制的方法,例如JSON.parse(JSON.stringify(obj))(尽管这种特定方法在对象具有循环引用时无法正常工作)。lodash的_.cloneDeep方法也可以很好地解决这个问题。

总结起来,直接修改React组件的状态会导致代码难以维护、失去对状态的控制,并且需要编写大量自定义代码。遵循React的不可变性原则可以使代码更优雅、更易测试,并且能够在生产环境中更好地保证代码的性能。

0