要在React的状态下修改嵌套很深的对象/变量,通常使用三种方法:指定,immutability-helper和cloneDeep从Lodash。
还有许多其他不太流行的第三方库可以实现这一点,但在这个回答中,我将只介绍这三个选项。此外,还存在一些附加的JavaScript方法,如数组扩展(参见@mpen的回答),但它们不是非常直观、易于使用,并且能够处理所有状态操作情况。
正如在投票最多的回答评论中无数次指出的那样,其作者建议直接改变状态:不要这样做。这是一个普遍存在的React反模式,它将不可避免地导致不必要的后果。学习正确的方法。
让我们比较三种广泛使用的方法。
给定这个状态对象结构:
state = {
outer: {
inner: 'initial value'
}
}
您可以使用以下方法更新最内部字段的值,而不影响状态的其余部分。
1. 香草JavaScript的Object.assign
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the shallow copying:', outer.inner) // initial value
const newOuter = Object.assign({}, outer, { inner: 'updated value' })
console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<main id="react"></main>
记住,那个对象。Assign不会执行深度复制,因为它只复制属性值,这就是为什么它所做的被称为浅复制(参见注释)。
要做到这一点,我们应该只操作基本类型(outer.inner)的属性,即字符串、数字、布尔值。
在本例中,我们使用Object创建了一个新常量(const newOuter…)。赋值,它创建一个空对象({}),将外部对象({inner: '初始值'})复制到该对象中,然后在其上复制一个不同的对象{inner: '更新值'}。
这样,最后新创建的newOuter常量将保存一个值{inner: 'updated value'},因为内部属性被覆盖了。这个newOuter是一个全新的对象,它没有链接到状态中的对象,因此可以根据需要进行更改,并且状态将保持不变,直到运行更新它的命令。
最后一部分是使用setOuter() setter将状态中的原始outer替换为新创建的newOuter对象(只有值会改变,属性名称outer不会改变)。
现在想象我们有一个更深层的状态,比如state = {outer: {inner: {innerMost: 'initial value'}}}。我们可以尝试创建newOuter对象,并用来自state, but object的外部内容填充它。assign函数不能将innerMost的值复制到这个新创建的newOuter对象,因为innerMost嵌套得太深了。
您仍然可以复制inner,就像上面的例子一样,但由于它现在是一个对象而不是一个原语,因此newOuter的引用。内部将被复制到外部。而不是inner,这意味着我们将以直接绑定到状态中的对象的局部newOuter对象结束。
这意味着在这种情况下,本地创建的newOuter发生了突变。内在会直接影响外在。内部对象(状态),因为它们实际上变成了相同的东西(在计算机的内存中)。
对象。因此,只有当您有一个相对简单的一级深度状态结构,其中最里面的成员持有原始类型的值时,Assign才有效。
如果你有更深层次的对象(第二层或更多),你应该更新,不要使用Object.assign。您有直接改变状态的风险。
2. Lodash的cloneDeep
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the deep cloning:', outer.inner) // initial value
const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
newOuter.inner = 'updated value'
console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<main id="react"></main>
Lodash的cloneDeep使用起来更简单。它执行深度克隆,因此如果您有一个相当复杂的状态,其中包含多级对象或数组,那么它是一个健壮的选项。只需要cloneDeep()顶级的状态属性,以任何您喜欢的方式改变克隆的部分,然后setOuter()将其恢复到状态。
3.immutability-helper
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
const update = immutabilityHelper
console.log('Before the deep cloning and updating:', outer.inner) // initial value
const newOuter = update(outer, { inner: { $set: 'updated value' } })
console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>
<main id="react"></main>
Immutability-helper将它带到了一个全新的水平,它的最酷之处在于它不仅可以$set值来状态项,还可以$push, $splice, $merge(等等)它们。下面是可用的命令列表。
一边笔记
同样,请记住,setOuter只修改状态对象的第一级属性(在这些示例中是outer),而不是深层嵌套的属性(outer.inner)。如果它以另一种方式表现,这个问题就不会存在。
哪个适合你的项目?
如果您不想或不能使用外部依赖项,并且有一个简单的状态结构,请坚持使用Object.assign。
如果你要操纵一个庞大或复杂的状态,Lodash的cloneDeep是一个明智的选择。
如果你需要高级功能,比如你的状态结构很复杂,你需要对它执行各种操作,试试immutability-helper,它是一种非常高级的工具,可以用于状态操作。
...或者,你真的需要这样做吗?
如果你在React的状态下保存了一个复杂的数据,也许这是一个考虑其他处理方法的好时机。在React组件中设置一个复杂的状态对象不是一个简单的操作,我强烈建议考虑不同的方法。
大多数情况下,您最好将复杂的数据保存在Redux存储中,使用reducer和/或sagas将其设置在那里,并使用选择器访问它。