- 什么是突变状态?
- 为什么react不推荐突变状态?
- 如何更合理的更新一个对象?
简单来说,一般情况下props
和state
发生变化时,才开启一个render
阶段,这是理想情况
因为react
知道:“oh!我又该工作了”
什么是突变?
当我们给setState
一个基本数据类型时,state
值将会是一个不可变的值
更新时,state
的原始值也不会被更改,而是重新创建一个不可变的的基本数据类型,以触发重新渲染
const [num,setNum] = setState(0)
// render
setNum(10)
复制代码
如果我们给setState
一个对象,试试呢?
从技术上讲可以改变对象本身的内容,我们将其称之为突变
const [name,setName] = useState({age:18})
name.age = 20
复制代码
然而,react
的状态在技术上是可变的,当react
仍然推荐使用不可变的方式去改变react
的状态
像基本数据类型一样,去始终替换他们,而不是去改变他们
为什么react
不推荐突变状态
-
调试:如果你使用
console.log
并且不改变状态,你过去的日子将不会被最近的状态破坏修改,你可以清楚的看到渲染之间的状态变化 -
优化:如果之前的
props
和state
和下一个状态相同,常见的react
优化策略将会跳过本次渲染,如果你从不改变状态,检查变化就会非常的块,如果prevProps === props
,react就可以确定它内部并没有发生变化 -
新功能:react正在构建的新功能依赖将状态视为快照,如果你正在更新过去的状态版本,这会导致无法使用新功能
-
需求变更:一些需要撤销/重做和显示历史记录的值,在没有突变的情况下更容易执行,这是因为你可以将过去的值保存在副本中,并在适用的情况下重做他们
-
更简单的实现:因为
react
不依赖突变,所以它不需要对你的对象做任何处理,不需要劫持你的对象。总是将它们包装到代理中,或者在初始化时像许多“反应式”解决方案那样做其他工作。这也是为什么react
允许您将任何对象置于状态(无论有多大)而没有额外的性能或正确性陷阱。
如何更合理的更新一个对象?
我们可以利用immer
封装一个hooks
immer
如何工作?
immer
提供的draft
是一种特殊类型的对象,称为Proxy
。它“记录”你用它做了什么。这就是为什么你可以随意改变它!在内部,immer
会找出哪些部分draft
已被更改,并生成一个包含您所做编辑的全新对象。
import produce, { Draft } from 'immer'
import { useState } from 'react'
// 定义一个将对象每个key都转为readonly的类型别名
// 后续并不真正的将对象标记为只读,而是在类型使用上进行约束
type DeepReadonly<T> = T extends object
? {
readonly [k in keyof T]: T[k] extends { [Key: string]: unknown } ? DeepReadonly<T[k]> : T[k]
}
: never
export function useImmutable<T extends object>(val: DeepReadonly<T>): [DeepReadonly<T>, (cb: (recipe: Draft<DeepReadonly<T>>) => void) => void] {
const [immutable, setImmutable] = useState<DeepReadonly<T>>(val)
function updateImmutable(cb: (updateImmutable: Draft<DeepReadonly<T>>) => void) {
// 通过cb回调更新immutable 生成一个新的 immutable state (nextState)
// 因为通过produce包装,所以我们无法对immutable进行操作
const nextState = produce(immutable, (cloneImmutable) => {
cb(cloneImmutable)
})
// 转换后的 nextState 用于更新
setImmutable(nextState)
}
return [immutable, updateImmutable]
}
const [profile,UpdateProfile] = useImmutable<{name:string}>({name:"as"})
profile.name = "error Update" // 此处会发生类型报错,该对象为只读对象
// 更新
UpdateProfile((nextState) => {
nextState.name = 'update'
})
复制代码
参考: