🤔react为什么需要不可变数据

3,340 阅读3分钟
  • 什么是突变状态?
  • 为什么react不推荐突变状态?
  • 如何更合理的更新一个对象?

简单来说,一般情况下propsstate发生变化时,才开启一个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 并且不改变状态,你过去的日志将不会被最近的状态破坏修改,你可以清楚的看到渲染之间的状态变化

  • 优化:如果之前的propsstate和下一个状态相同,常见的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'
})

参考: