https://github.com/immerjs/immer
immer 英文 https://immerjs.github.io/immer
immer 中文 https://immerjs.github.io/immer/zh-CN
image.pngpnpm add immer

immer原理

  1. 核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构
  2. immer工作原理就是把 produce方法传入的第一个参数作为初始值initialState
    1. produce方法里面会通过createProxy这个方法创建一个代理对象
  3. 通过es6的 Proxy进行代理返回一个 draft代理对象
    1. 如果当前环境不支持 Proxy则会使用Object.defineProperty()实现监听
  4. 进行get、set的拦截把只有改变了的部分会从 draft映射到初始对象当中

解决的痛点

  1. 解决对象引用问题,cloneDeep的性能问题
    1. 深拷贝一次之前的对象,然后在这个新对象的基础上修改属性,最后进行 setState
  2. 在修改一个非常复杂的 state属性的时候,可能需要不断地使用扩展语法,这样会导致错误的几率上升

immer缺点

Immer 默认情况下必须启用自动冻结功能,
如果禁用自动冻结功能,性能会变得特别差,这使得 Immer 的 immutable 状态并不够通用。
在跨进程、远程数据传输等场景下,我们必须不断冻结这些 immutable 数据

Proxy 的 getitem、setitem 惰性求值,不能降低更新的时间复杂度;

  1. 如果要维护一个比较大的可持久化数据结构,如一个长度为 1e5 的完全可持久化数组,需要用 immutable.js
  2. immutable.js缺点
    1. 数据无法序列化,前期必须 fromJS,后期必须 toJS 转成js对象
    2. https://www.zhihu.com/question/266511546

immer 属性

  1. import { produce } from "immer";
  2. const reducer = produce(state, draft => {
  3. draft.user = 'lucy';
  4. });
  5. function reducer(action, payload) {
  6. return produce(state, draft => {
  7. draft.user = paylaod;
  8. })
  9. };
  10. // setState 函数柯里化写法
  11. const _state = produce(state => {
  12. state.todoList.push({ name: 'immer源码', done: true })
  13. });
  14. setState(_state)

immer参考
https://segmentfault.com/a/1190000017270785
https://www.jianshu.com/p/8970a820c148

immer更新数据

immer更新数组

https://juejin.cn/post/7157745748832944141

  1. import produce from 'immer';
  2. const todosArray = [
  3. { id: 'id1', done: false, body: 'Take out the trash' },
  4. { id: 'id2', done: false, body: 'Check Email' },
  5. ];
  6. // 添加
  7. const addedTodosArray = produce(todosArray, draft => {
  8. draft.push({ id: 'id3', done: false, body: 'Buy bananas' });
  9. });
  10. // 索引删除
  11. const deletedTodosArray = produce(todosArray, draft => {
  12. draft.splice(3 /*索引 */, 1);
  13. });
  14. // 索引更新
  15. const updatedTodosArray = produce(todosArray, draft => {
  16. draft[3].done = true;
  17. });
  18. // 索引插入
  19. const updatedTodosArray = produce(todosArray, draft => {
  20. draft.splice(3, 0, { id: 'id3', done: false, body: 'Buy bananas' });
  21. });
  22. // 删除最后一个元素
  23. const updatedTodosArray = produce(todosArray, draft => {
  24. draft.pop();
  25. });
  26. // 删除第一个元素
  27. const updatedTodosArray = produce(todosArray, draft => {
  28. draft.shift();
  29. });
  30. // 数组开头添加元素
  31. const addedTodosArray = produce(todosArray, draft => {
  32. draft.unshift({ id: 'id3', done: false, body: 'Buy bananas' });
  33. });
  34. // 根据 id 删除
  35. const deletedTodosArray = produce(todosArray, draft => {
  36. const index = draft.findIndex(todo => todo.id === 'id1');
  37. if (index !== -1) {
  38. draft.splice(index, 1);
  39. }
  40. });
  41. // 根据 id 更新
  42. const updatedTodosArray = produce(todosArray, draft => {
  43. const index = draft.findIndex(todo => todo.id === 'id1');
  44. if (index !== -1) {
  45. draft[index].done = true;
  46. }
  47. });
  48. // 过滤
  49. const updatedTodosArray = produce(todosArray, draft => {
  50. // 过滤器实际上会返回一个不可变的状态,但是如果过滤器不是处于对象的顶层,这个依然很有用
  51. return draft.filter(todo => todo.done);
  52. });

useImmer

pnpm add immer use-immer
https://github.com/immerjs/use-immer
useImmer 就只是 useState 进行了一层包装,
将修改这个回调用 produce 包装了一层,才使得在外面使用的时候能够直接修改对象下面的属性

  1. import React from "react";
  2. import { useImmer } from "use-immer";
  3. function App() {
  4. const [person, updatePerson] = useImmer({
  5. name: "Michel",
  6. age: 33
  7. });
  8. function updateName(name) {
  9. updatePerson(draft => {
  10. draft.name = name;
  11. });
  12. }
  13. function becomeOlder() {
  14. updatePerson(draft => {
  15. draft.age++;
  16. });
  17. }
  18. return (
  19. <div className="App">
  20. Hello {person.name} ({person.age})
  21. <input
  22. onChange={e => {
  23. updateName(e.target.value);
  24. }}
  25. value={person.name}
  26. />
  27. <br />
  28. <button onClick={becomeOlder}>Older</button>
  29. </div>
  30. );
  31. }

手写 useImmer

  1. import { produce, Draft, nothing, freeze } from "immer";
  2. export function useImmer(initialValue: any) {
  3. const [val, updateValue] = useState(() =>
  4. freeze(
  5. typeof initialValue === "function" ? initialValue() : initialValue,
  6. true
  7. )
  8. );
  9. return [
  10. val,
  11. useCallback((updater) => {
  12. if (typeof updater === "function") updateValue(produce(updater));
  13. else updateValue(freeze(updater));
  14. }, []),
  15. ];
  16. }

useImmerReducer

  1. import { useImmerReducer } from "use-immer";
  2. const initialState = { count: 0 };
  3. function reducer(draft, action) {
  4. switch (action.type) {
  5. case "reset":
  6. return initialState;
  7. case "increment":
  8. return void draft.count++;
  9. case "decrement":
  10. return void draft.count--;
  11. }
  12. }
  13. function Counter() {
  14. const [state, dispatch] = useImmerReducer(reducer, initialState);
  15. return (
  16. <>
  17. Count: {state.count}
  18. <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
  19. <button onClick={() => dispatch({ type: "increment" })}>+</button>
  20. <button onClick={() => dispatch({ type: "decrement" })}>-</button>
  21. </>
  22. );
  23. }

immer redux

  1. import produce from 'immer';
  2. const namespace = 'LIST/'
  3. export const CHANGE_LIST = `${namespace}CHANGE`
  4. const defaultState = {
  5. dataSource: [],
  6. title: 'LIST'
  7. }
  8. export function listReducer(state = defaultState, action) {
  9. return produce(state, draft => {
  10. const { type, payload } = action;
  11. switch (type) {
  12. case CHANGE_LIST:
  13. draft.dataSource = payload.dataSource
  14. draft.title = payload.title
  15. break;
  16. default:
  17. break;
  18. }
  19. console.log('action', action, 'draft', draft)
  20. })
  21. }

不可变数据

  1. 其实就是当你修改一个数据的时候,这个数据会给你返回一个新的引用,而自己的引用保持不变,
    1. 有点像是经常用到的数组的map
  2. Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce``find函数式操作方法。同时 API 也设计的和JS对象、数组等类似
  3. immer来代替immutable https://juejin.im/post/6844904035409985549
  4. immerjs讲解 https://segmentfault.com/a/1190000017270785
  5. https://juejin.cn/post/6844903782145327118

解决引用类型对象被修改的办法

  1. 深度拷贝,但是深拷贝的成本较高,会影响性能;
  2. ImmutableJS,非常棒的一个不可变数据结构的库,可以解决上面的问题
  3. But,跟 Immer 比起来,ImmutableJS 有两个较大的不足:
    1. 需要使用者学习它的数据结构操作方式,没有 Immer 提供的使用原生对象的操作方式简单、易用;
    2. 它的操作结果需要通过toJS方法才能得到原生对象,
    3. 在操作一个对象的时候,要时刻注意操作的是原生对象还是 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/