https://github.com/immerjs/immer
immer 英文 https://immerjs.github.io/immer
immer 中文 https://immerjs.github.io/immer/zh-CN
pnpm add immer
immer原理
- 核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构
- immer工作原理就是把 produce方法传入的第一个参数作为初始值initialState
- produce方法里面会通过createProxy这个方法创建一个代理对象
- 通过es6的 Proxy进行代理返回一个 draft代理对象
- 如果当前环境不支持 Proxy则会使用Object.defineProperty()实现监听
- 进行get、set的拦截把只有改变了的部分会从 draft映射到初始对象当中
解决的痛点
- 解决对象引用问题,cloneDeep的性能问题
- 深拷贝一次之前的对象,然后在这个新对象的基础上修改属性,最后进行 setState
- 在修改一个非常复杂的 state属性的时候,可能需要不断地使用扩展语法,这样会导致错误的几率上升
immer缺点
Immer 默认情况下必须启用自动冻结功能,
如果禁用自动冻结功能,性能会变得特别差,这使得 Immer 的 immutable 状态并不够通用。
在跨进程、远程数据传输等场景下,我们必须不断冻结这些 immutable 数据
Proxy 的 getitem、setitem 惰性求值,不能降低更新的时间复杂度;
- 如果要维护一个比较大的可持久化数据结构,如一个长度为 1e5 的完全可持久化数组,需要用 immutable.js
- immutable.js缺点
- 数据无法序列化,前期必须 fromJS,后期必须 toJS 转成js对象
- https://www.zhihu.com/question/266511546
immer 属性
import { produce } from "immer";
const reducer = produce(state, draft => {
draft.user = 'lucy';
});
function reducer(action, payload) {
return produce(state, draft => {
draft.user = paylaod;
})
};
// setState 函数柯里化写法
const _state = produce(state => {
state.todoList.push({ name: 'immer源码', done: true })
});
setState(_state)
immer参考
https://segmentfault.com/a/1190000017270785
https://www.jianshu.com/p/8970a820c148
immer更新数据
immer更新数组
https://juejin.cn/post/7157745748832944141
import produce from 'immer';
const todosArray = [
{ id: 'id1', done: false, body: 'Take out the trash' },
{ id: 'id2', done: false, body: 'Check Email' },
];
// 添加
const addedTodosArray = produce(todosArray, draft => {
draft.push({ id: 'id3', done: false, body: 'Buy bananas' });
});
// 索引删除
const deletedTodosArray = produce(todosArray, draft => {
draft.splice(3 /*索引 */, 1);
});
// 索引更新
const updatedTodosArray = produce(todosArray, draft => {
draft[3].done = true;
});
// 索引插入
const updatedTodosArray = produce(todosArray, draft => {
draft.splice(3, 0, { id: 'id3', done: false, body: 'Buy bananas' });
});
// 删除最后一个元素
const updatedTodosArray = produce(todosArray, draft => {
draft.pop();
});
// 删除第一个元素
const updatedTodosArray = produce(todosArray, draft => {
draft.shift();
});
// 数组开头添加元素
const addedTodosArray = produce(todosArray, draft => {
draft.unshift({ id: 'id3', done: false, body: 'Buy bananas' });
});
// 根据 id 删除
const deletedTodosArray = produce(todosArray, draft => {
const index = draft.findIndex(todo => todo.id === 'id1');
if (index !== -1) {
draft.splice(index, 1);
}
});
// 根据 id 更新
const updatedTodosArray = produce(todosArray, draft => {
const index = draft.findIndex(todo => todo.id === 'id1');
if (index !== -1) {
draft[index].done = true;
}
});
// 过滤
const updatedTodosArray = produce(todosArray, draft => {
// 过滤器实际上会返回一个不可变的状态,但是如果过滤器不是处于对象的顶层,这个依然很有用
return draft.filter(todo => todo.done);
});
useImmer
pnpm add immer use-immer
https://github.com/immerjs/use-immer
useImmer 就只是 useState 进行了一层包装,
将修改这个回调用 produce 包装了一层,才使得在外面使用的时候能够直接修改对象下面的属性
import React from "react";
import { useImmer } from "use-immer";
function App() {
const [person, updatePerson] = useImmer({
name: "Michel",
age: 33
});
function updateName(name) {
updatePerson(draft => {
draft.name = name;
});
}
function becomeOlder() {
updatePerson(draft => {
draft.age++;
});
}
return (
<div className="App">
Hello {person.name} ({person.age})
<input
onChange={e => {
updateName(e.target.value);
}}
value={person.name}
/>
<br />
<button onClick={becomeOlder}>Older</button>
</div>
);
}
手写 useImmer
import { produce, Draft, nothing, freeze } from "immer";
export function useImmer(initialValue: any) {
const [val, updateValue] = useState(() =>
freeze(
typeof initialValue === "function" ? initialValue() : initialValue,
true
)
);
return [
val,
useCallback((updater) => {
if (typeof updater === "function") updateValue(produce(updater));
else updateValue(freeze(updater));
}, []),
];
}
useImmerReducer
import { useImmerReducer } from "use-immer";
const initialState = { count: 0 };
function reducer(draft, action) {
switch (action.type) {
case "reset":
return initialState;
case "increment":
return void draft.count++;
case "decrement":
return void draft.count--;
}
}
function Counter() {
const [state, dispatch] = useImmerReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
immer redux
import produce from 'immer';
const namespace = 'LIST/'
export const CHANGE_LIST = `${namespace}CHANGE`
const defaultState = {
dataSource: [],
title: 'LIST'
}
export function listReducer(state = defaultState, action) {
return produce(state, draft => {
const { type, payload } = action;
switch (type) {
case CHANGE_LIST:
draft.dataSource = payload.dataSource
draft.title = payload.title
break;
default:
break;
}
console.log('action', action, 'draft', draft)
})
}
不可变数据
- 其实就是当你修改一个数据的时候,这个数据会给你返回一个新的引用,而自己的引用保持不变,
- 有点像是经常用到的数组的map
- Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce``find函数式操作方法。同时 API 也设计的和JS对象、数组等类似
- immer来代替immutable https://juejin.im/post/6844904035409985549
- immerjs讲解 https://segmentfault.com/a/1190000017270785
- https://juejin.cn/post/6844903782145327118
解决引用类型对象被修改的办法
- 深度拷贝,但是深拷贝的成本较高,会影响性能;
- ImmutableJS,非常棒的一个不可变数据结构的库,可以解决上面的问题
- But,跟 Immer 比起来,ImmutableJS 有两个较大的不足:
- 需要使用者学习它的数据结构操作方式,没有 Immer 提供的使用原生对象的操作方式简单、易用;
- 它的操作结果需要通过
toJS
方法才能得到原生对象, - 在操作一个对象的时候,要时刻注意操作的是原生对象还是 ImmutableJS 的返回结果,稍不注意,就会产生意想不到的 bug
immer替代品,性能优于 immer 10x
https://github.com/unadlib/mutative
https://zhuanlan.zhihu.com/p/422445526
https://juejin.cn/post/7218179400633253947
https://segmentfault.com/a/1190000017270785
https://zhangzhao.name/posts/immer-immutable/