为了在 React 状态中修改深度嵌套的对象/变量,通常使用三种方法:vanilla JavaScript's 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()
setterouter
用一个新创建的newOuter
对象替换原来在state中的(只会改变值,不会改变属性名outer
)。
现在想象我们有一个更深的状态,比如state = { outer: { inner: { innerMost: 'initial value' } } }
。我们可以尝试创建newOuter
对象并用outer
状态中的内容填充它,但由于嵌套太深,Object.assign
无法将innerMost
的值复制到这个新创建的newOuter
对象innerMost
。
你仍然可以复制inner
,就像上面的例子一样,但是因为它现在是一个对象而不是一个原始对象,引用fromnewOuter.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
.
如果你操纵一个巨大和/或复杂的 state,LodashcloneDeep
是一个明智的选择。
如果您需要高级功能,即如果您的状态结构很复杂并且您需要对其执行各种操作,请尝试immutability-helper
,这是一个非常高级的工具,可用于状态操作。
……或者,你真的需要这样做吗?
如果你在 React 的状态下持有一个复杂的数据,也许现在是考虑其他处理它的方式的好时机。在 React 组件中设置一个复杂的状态对象并不是一个简单的操作,我强烈建议考虑不同的方法。
很可能您最好不要将复杂数据保存在 Redux 存储中,使用 reducer 和/或 sagas 将其设置在那里,并使用选择器访问它。