什么是immer.js
- 使用 es6 Proxy实现的不可变数据类型,Immutability 替代方案。
什么是不可变数据类型
- 不可变数据,就是值的数据无法被修改,改动数据的时候会创建一个新值。
- 说白了就是数据的克隆,克隆的方式有很多种。可以参考该文章 循环克隆
- 说到不可变数据,就得说起函数式编程。immer.js 就是 react 实现不可变数据的一种方式
- 不可变数据是函数式编程的 安全帽 ,在js 中 如果函数传入的参数是复杂数据类型,没有经过克隆等手段,改动参数就可能 会产生副作用。
immer.js 的使用
import produce from "immer"
const baseState = [
{
title: "Learn TypeScript",
done: true
},
{
title: "Try Immer",
done: false
}
]
const nextState = produce(baseState, draftState => {
draftState.push({title: "Tweet about it"})
draftState[1].done = true
})
react 中使用
import React, { useCallback, useState } from "react";
import {produce} from "immer";
const TodoList = () => {
const [todos, setTodos] = useState([
{
id: "React",
title: "Learn React",
done: true
},
{
id: "Immer",
title: "Try Immer",
done: false
}
]);
const handleToggle = useCallback((id) => {
setTodos(
produce((draft) => {
const todo = draft.find((todo) => todo.id === id);
todo.done = !todo.done;
})
);
}, []);
const handleAdd = useCallback(() => {
setTodos(
produce((draft) => {
draft.push({
id: "todo_" + Math.random(),
title: "A new todo",
done: false
});
})
);
}, []);
return (<div>{*/ See CodeSandbox */}</div>)
}
Proxy 的 妙用
代理对象
const obj = {
a: 1,
b: {
c: 222
}
}
const ProxyObj = new Proxy(obj, {
get(state, key) {
console.log(state, key, '--.')
return state[key]
},
set(target, attr, value) {
return value
}
})
ProxyObj.b.c = 10

- 我们 在 设置 obj.b.c 的值时候,首先会访问到b, 所以 控制台 在get 的时候 打印 的 key 是 b。
- 但是由于proxy 是 浅代理,所以 b 这个对象是没有被代理过,就没有任何输出了。
getter + 递归 深度代理对象
const obj = {
a: 1,
b: {
c: 222,
d: {
e: 1
}
}
}
const createProxy = (data) => {
const ProxyObj = new Proxy(data, {
get(state, key) {
console.log(state, key, '--.')
return createProxy(state[key])
},
set(target, attr, value) {
return Reflect.set(target, attr, value)
}
})
return ProxyObj
}
const ProxyObj = createProxy(obj)
ProxyObj.b.d.e = 10

- 我们在设置 .b.d.e = 10 的时候。访问了 b, d 对象,在get 的都打印了。并且 b, d 都成了代理对象,其他还是个正常对象。
immer 的 核心思想
- 比如我们要改.b.d.e = 10。最终要改的 只是 d 对象上的 e,其他属性我们都不会去操作它。那么我们可以不可以把 d 对象 拷贝 一份 ,得到一个新的 d 对象呢?
const d = { e: 1 }
const newD = JSON.parse(JSON.stringify(d))
const newObj = {
a: 1,
b: {
c: 222,
d: newD
}
}
- 答案是肯定的,并且immer 也是这样做的,newObj 就是通过immer 处理后返回来的 对象。对象里面 除了d 属性 其他的属性都和 obj 属性 画等号。
- 因为 newObj 里面的 d 是经过克隆的,那么 newObj.b.d.e = 10 是不是就不会影响到 obj 里面e 的值呢。这样就最小化的实现了一个 不可变数据
- 这一小小设计体现了 “按需” 软件设计思想,按需代理。在数据对象特别庞大的时候,这种方式实现的数据不可变性能是最优的。
immer 代理的核心代码
const deepClone = (data, cb) => {
const proxyData = createProxy(data)
cb(proxyData)
return proxyData
}
const createProxy = (data) => {
const target = {
base: data,
copy: null
}
const handle = {
set(target, attr, value) {
target.copy = {
...target.base,
[attr]: value,
}
return target
},
get(state, key) {
if (key === 'copy' || key === 'base') {
return state[key] || state.base
}
if (typeof state.base[key] === 'object') {
if (state.copy === null) {
state.copy = { ...state.base, }
}
state.copy[key] = createProxy(state.base[key])
return state.copy[key]
}
return state.base[key]
}
}
const { proxy } = Proxy.revocable(target, handle)
return proxy
}
export default deepClone
const baseObj = {
a: { b: 1, c: { d: 4 } },
name: 'ajin',
age: 10,
hh: {
d: 9
}
}
const nextState = deepClone(baseObj, (draftState) => {
draftState.a.c.d = 3
})
console.log(baseObj, nextState)
- 输出结果

- 以上就是immer 实现不可变数据的核心思想。Proxy 在 我们开发过程中很常见,多了解Proxy 的经典应用能在开发中使用得更加得心应手。