type
status
date
slug
summary
category
password
tags
icon
之前一直不理解react中的state为什么不能像vue中的reactive一样,只要state的属性发生改变(如果state是对象),就会触发页面渲染,今天算是明白react为什么不那样做了。
📝 state 如何更新对象?
state 中可以保存任意类型的 JavaScript 值,包括对象。但是,你不应该直接修改存放在 React state 中的对象。相反,当你想要更新一个对象时,你需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。
🤔 什么是 mutation?
你可以在 state 中存放任意类型的 JavaScript 值。
到目前为止,你已经尝试过在 state 中存放数字、字符串和布尔值,这些类型的值在 JavaScript 中是不可变(immutable)的,这意味着它们不能被改变或是只读的。你可以通过替换它们的值以触发一次重新渲染。
state
x
从 0
变为 5
,但是数字 0
本身并没有发生改变。在 JavaScript 中,无法对内置的原始值,如数字、字符串和布尔值,进行任何更改。现在考虑 state 中存放对象的情况:
从技术上来讲,可以改变对象自身的内容。当你这样做时,就制造了一个 mutation:
然而,虽然严格来说 React state 中存放的对象是可变的,但你应该像处理数字、布尔值、字符串一样将它们视为不可变(immutable)的。因此你应该替换它们的值,而不是对它们进行修改。
因此正确的做法是:
😋如何更新一个嵌套对象
考虑下面这种结构的嵌套对象:
如果你想要更新
person.artwork.city
的值,用 mutation 来实现的方法非常容易理解:但是在 React 中,你需要将 state 视为不可变的!为了修改
city
的值,可以使用…
运算符:🤨为什么在 React 中不推荐直接修改 state?
- 调试:如果你使用
console.log
并且不直接修改 state,你之前日志中的 state 的值就不会被新的 state 变化所影响。这样你就可以清楚地看到两次渲染之间 state 的值发生了什么变化
- 优化:React 常见的 优化策略 依赖于如果之前的 props 或者 state 的值和下一次相同就跳过渲染。如果你从未直接修改 state ,那么你就可以很快看到 state 是否发生了变化。如果
prevObj === obj
,那么你就可以肯定这个对象内部并没有发生改变。
- 新功能:我们正在构建的 React 的新功能依赖于 state 被 像快照一样看待 的理念。如果你直接修改 state 的历史版本,可能会影响你使用这些新功能。
- 需求变更:有些应用功能在不出现任何修改的情况下会更容易实现,比如实现撤销/恢复、展示修改历史,或是允许用户把表单重置成某个之前的值。这是因为你可以把 state 之前的拷贝保存到内存中,并适时对其进行再次使用。如果一开始就用了直接修改 state 的方式,那么后面要实现这样的功能就会变得非常困难。
- 更简单的实现:React 并不依赖于 mutation ,所以你不需要对对象进行任何特殊操作。它不需要像很多“响应式”的解决方案(点名vue)一样去劫持对象的属性、总是用代理把对象包裹起来,或者在初始化时做其他工作。这也是为什么 React 允许你把任何对象存放在 state 中——不管对象有多大——而不会造成有任何额外的性能或正确性问题的原因。
总结
- 将 React 中所有的 state 都视为不可直接修改的。
- 当你在 state 中存放对象时,直接修改对象并不会触发重渲染,并会改变前一次渲染“快照”中 state 的值。
- 不要直接修改一个对象,而要为它创建一个新版本,并通过把 state 设置成这个新版本来触发重新渲染。
- 你可以使用这样的
{...obj, something: 'newValue'}
对象展开语法来创建对象的拷贝。
- 对象的展开语法是浅层的:它的复制深度只有一层。想要更新嵌套对象,你需要从你更新的位置开始自底向上为每一层都创建新的拷贝。
📎 参考文章
- 作者:NotionNext
- 链接:https://lsnx.top/article/updating-state
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。