为什么我不能直接修改组件的状态,真的吗?
为什么我不能直接修改组件的状态,真的吗?
我知道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
文档下的注释基本上指出了两个问题:
- 如果你直接改变状态然后随后调用
this.setState
,这可能会替换(覆盖?)你所做的改变。我不明白这在上面的代码中如何发生。 setState
可能以异步/延迟的方式改变this.state
,因此在调用this.setState
之后立即访问this.state
时,无法保证访问到最终改变的状态。我理解这一点,但如果this.setState
是更新函数的最后一次调用,这不是一个问题。
更新组件状态时,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}
为什么不能直接修改组件的状态,真的为什么?
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
的直接更改”。
为什么不能直接修改组件的状态?
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的不可变性原则可以使代码更优雅、更易测试,并且能够在生产环境中更好地保证代码的性能。