要在React状态下修改深层嵌套的对象/变量,通常使用三种方法:vanilla JavaScript的Object.assign
,immutability -helper和cloneDeep
来自Lodash的方法。
还有许多其他较不受欢迎的第三方库可以实现此目的,但是在此答案中,我将仅介绍这三个选项。此外,还存在一些其他的普通JavaScript方法,例如数组扩展(例如,参见@mpen的答案),但是它们不是非常直观,易于使用并且能够处理所有状态操纵情况。
正如人们在回答问题的最高票数评论中指出的那样,无数次,他们的作者提出了国家的直接突变:只是不要那样做。这是无处不在的React反模式,将不可避免地导致不良后果。学习正确的方法。
让我们比较三种广泛使用的方法。
给定此状态对象结构:
state = {
outer: {
inner: 'initial value'
}
}
您可以使用以下方法更新最里面的 inner
字段的值,而不会影响其余状态。
1. Vanilla 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>
请记住,Object.assign 将不执行深度克隆,因为它仅复制属性值,因此这就是所谓的浅复制(请参见注释)的原因。
为此,我们应该只操作基本类型(outer.inner
)的属性,即字符串,数字,布尔值。
在这个例子中,我们创建一个新的常数(const newOuter...
),使用Object.assign
,它创建一个空的对象({}
),拷贝outer
对象({ inner: 'initial value' }
)到它,然后复制一个不同的对象{ inner: 'updated value' }
了吧。
这样,最后newOuter
,{ inner: 'updated value' }
由于该inner
属性被覆盖,新创建的常量将保留一个值。这newOuter
是一个全新的对象,它没有链接到处于状态的对象,因此可以根据需要对其进行更改,并且状态将保持不变,并且在运行更新该对象的命令之前不会更改。
最后一部分是使用setOuter()
setter outer
将状态为原始的newOuter
对象替换为新创建的对象(仅值会更改,属性名称outer
不会)。
现在想象我们有一个更深的状态,如state = { outer: { inner: { innerMost: 'initial value' } } }
。我们可以尝试创建该newOuter
对象并使用outer
状态中的内容填充它,但是由于以下原因,Object.assign
将无法将innerMost
的值复制到此新创建的newOuter
对象中:innerMost
嵌套太深。
您仍然可以inner
像上面的示例中那样进行复制,但是由于它现在是对象而不是原始对象,因此from 的引用newOuter.inner
将被复制到对象outer.inner
,这意味着我们最终将newOuter
在状态下将本地对象直接绑定到该对象。
这意味着在这种情况下,本地创建的突变newOuter.inner
将直接影响outer.inner
对象(处于状态),因为实际上它们已成为同一事物(位于计算机的内存中)。
Object.assign
因此,仅当您具有一个相对简单的一级深度状态结构且其最内部的成员保存原始类型的值时,该结构才起作用。
如果您有更深的对象(第2级或更高级别)需要更新,请不要使用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. 不变性的帮助者
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在那里进行设置,并使用选择器进行访问。