react官网推荐库use-immer:https://www.npmjs.com/package/use-immer
引入:import { useImmer } from "use-immer";
优点:
- 简化代码: 只需要关注需要变动的部分,而 immer 本身将在后台处理其余部分。
- 学习成本和替换代价小
一、对象
假如现在有一个嵌套结构的对象:
const personList = {
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: {
url: 'https://i.imgur.com/Sd1AgUOm.jpg',
alt: 'test',
}
}
}
正常写法
const [person, setPerson] =
useState(personList)
setPerson({
...person,
artwork: {
...person.artwork,
image: {
...person.artwork.image,
alt: 'test1'
}
}
});
使用use-immer:
const [person, updatePerson] =
useImmer(personList)
updatePerson(draft => {
draft.artwork.image.alt = 'test1';
});
- draft:一种特殊类型的对象,Proxy,可以保留一份之前的对象
- Immer 找出哪些部分draft已被更改,并生成一个包含您的编辑的全新对象。
二、数据的双向绑定:immer中的核心 api ——produce
Immer.js
是 mobx
的作者写的一个 Immutable(不可变数据) 库,核心实现是利用 ES6 的proxy
,几乎以最小的成本实现了JavaScript的不可变数据结构。
api介绍:
produce(baseState, recipe: (draftState) => void): nextState
基本概念:
- baseState:被操作对象的最初状态
- draftState: 根据
currentState
生成的草稿、是currentState
的代理、对draftState
所有的修改都被记录并用于生成nextState
。在此过程中,currentState
不可变 - recipe:用于操作
draftState
的函数 - nextState: 根据
draftState
生成的最终状态 - produce: 用于生成
nextState
或者producer
的函数
import produce from "immer"
const baseState = personList
const nextState = produce(baseState, draftState => {
draftState.artwork.image.alt = 'test1';
})
对draftState
的修改最终都会体现在nextState
,但并不会修改baseState
,
需要注意的是nextState
和baseState
共享未修改的部分。通过produce
生成的nextState
是被冻结的(使用Object.freeze实现,仅冻结nextState
和currentState
相比更改的部分),直接修改nextstate
会报错。
2.1 Proxy 对象
用于创建一个对象的代理,实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
Proxy 对象接受两个参数,
- 第一个参数:需要操作的对象
- 第二个参数是设置对应拦截的属性,支持
get
,set
(劫持了对应元素的读和写),能够在其中进行一些操作,最终返回一个 Proxy 对象实例
const handle = {
get(target, key) {
// 这里的 target 就是 Proxy 的第一个参数对象
console.log('proxy get key', key)
return '返回1'
},
set(target, key, value) {
console.log('proxy set key', value)
}
}
const target = {a:1}
const p = new Proxy(target, handle)
p.a = 2 // 所有设置操作都被转发到了 set 方法内部
2.2 produce
源码:
主流程主要根据base
创建draft
对象、执行用户传入的recipe
拦截读写操作,走到自定义的getter
和setter
最后再解析组装结果返回给用户。
2.3 hook——useImmer
use-Immer
库把上述setState+produce的用法封装起来。
它接收一个初始状态,返回一个数组。
数组解构出的第一个值为当前状态,第二个值为状态更新函数(produce
中的 recipe
)。
“一”中修改后的🌰:
// 一般将 setxxx => updatexx
const [person, updatePerson] = useImmer(personList)
// recipe:用于操作draftState的函数
updatePerson(draft => {
draft.artwork.image.alt = 'test1';
});
三、数组
在 JavaScript 中,数组只是另一种对象,应该将处于 React 状态的数组视为只读,
每次您想更新一个数组时,您都需要将一个新数组传递给您的状态设置函数。
使用immer: 可以使用两列的方法
const [artists, setArtists] = useState([]);
举例
setArtists(
[
...artists,
{ id: '1', name: name }
]
);
或者
updateMyList(draft => {
//push/unshift
draft.push({ id: '1', name: name })
});