React官方文档、Immer官方文档、use-immer
state 中可以保存任意类型的 JavaScript 值,包括对象。但是,你不应该直接修改存放在 React state 中的对象。相反,当你想要更新一个对象时,你需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。
从上面我们可以知道React中强调数据是不可变的(Immutable)。
前言:Immer 是一个非常流行的库,它可以让你使用简便但可以直接修改的语法编写代码,并会帮你处理好复制的过程
安装库:npm install immer
const baseState = [
{
title: "Learn TypeScript",
done: true
},
{
title: "Try Immer",
done: false
}
]
const nextState = produce(baseState, draft => {
draft[1].done = true
})
console.log(Object.is(baseState,nextState)) //false
import React, { useCallback, useState } from 'react'
import { produce } from "immer"
const index: React.FC = () => {
const [list, setList] = useState([{ id: 1 }, { id: 2 }])
const add = useCallback(() => {
setList(produce(draft => {
const id = draft[draft.length - 1].id + 1
draft.push({ id })
}))
}, [])
return (
<>
<div>
<p>immer:</p>
{list.map(item => <div key={item.id}>id:{item.id}</div>)}
<button onClick={add}>add</button>
</div>
</>
)
}
export default index
注:以上仅对immer实现原理的简要分析,详细可见官方库源码
import React, { useCallback, useState } from 'react'
import { useImmer } from 'use-immer'
const index: React.FC = () => {
const [list, setList] = useImmer([{ id: 1 }, { id: 2 }])
const add = useCallback(() => {
setList(draft => {
const id = draft[draft.length - 1].id + 1
draft.push({ id })
})
}, [])
return (
<div>
<p>use-immer:</p>
{list.map(item => <div key={item.id}>id:{item.id}</div>)}
<button onClick={add}>add</button>
</div>
)
}
export default index
补充:use-immer中还配有Reducer相关的Hook
import { useCallback, useState } from 'react'
import { produce, freeze, Draft } from "immer"
export type DraftFunction<S> = (draft: Draft<S>) => void;
export type Updater<S> = (arg: S | DraftFunction<S>) => void;
export type ImmerHook<S> = [S, Updater<S>];
export function useImmer<S = any>(initialValue: S | (() => S)): ImmerHook<S>;
export function useImmer(initialValue: any) {
const [value, setValue] = useState(
// 使用 freeze 深度冻结变量,表示值不可修改
freeze(typeof initialValue === 'function' ? initialValue() : initialValue, true)
)
const updateValue = useCallback((updater: any) => {
if (typeof updater === "function") {
setValue(produce(updater))
} else {
updateValue(freeze(updater))
}
}, [])
return [value, updateValue]
}