immer 探索

https://github.com/immerjs/immer (opens in a new tab)

源码略难懂,看不下去啦嘿嘿

能做什么

allows you to work with immutable state in a more convenient way.

在很多单向数据流场景,我们都需要将最新的 state 返回给下一个 status,会遇到多层嵌套对象需要改属性的场景

return {
  ...state,
  foo: {
    ...state.foo,
    gar: 123,
  },
};

Immer 是能够通过记录你草稿的方式让不可变数据能更好的被修改!

import produce from "immer";
 
const nextState = produce(baseState, (draft) => {
  draft[1].done = true;
  draft.push({ title: "Tweet about it" });
});

兼容性问题 (opens in a new tab)

for old JS engine

若要支持 es5 需要开启 enableES5()

使用了 Map 或者 Set 需要 enableMapSet()!看了下源码是在 produce 的时候构造 DraftMap/DraftSet(继承了 Map/Set)

使用 JSON Patch enablePatches()

一键开启所有 enableAllPlugins()

相当于是内置 polyfill,会增大一定的包体积(每个插件都 < 1KB gzip,vanilla immer 有 ~3KB gzipped)

使用

basic produce

produce(baseState, recipe: (draftState) => void): nextState

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;
});

Curried produce

对于 state 的操作,可能会再次封装从而改变想要改变的属性,produce 提供了 currying 的做法

import produce from "immer";
 
// curried producer:
const toggleTodo = produce((draft, id) => {
  const todo = draft.find((todo) => todo.id === id);
  todo.done = !todo.done;
}); // -> (state: T) => nextState: T
 
const baseState = [
  /* as is */
];
 
const nextState = toggleTodo(baseState, "Immer");

React & Immer

useImmer (opens in a new tab)

可以自动的将 update function 包裹在 produce

也可以用 useImmerReducer

import React, { useCallback } from "react";
import { useImmer } from "use-immer";
 
const TodoList = () => {
  const [todos, setTodos] = useImmer([
    {
      id: "React",
      title: "Learn React",
      done: true
    },
    {
      id: "Immer",
      title: "Try Immer",
      done: false
    }
  ]);
 
  const handleToggle = useCallback((id) => {
    setTodos((draft) => {
      const todo = draft.find((todo) => todo.id === id);
      todo.done = !todo.done;
    });
  }, []);
 
  const handleAdd = useCallback(() => {
    setTodos((draft) => {
      draft.push({
        id: "todo_" + Math.random(),
        title: "A new todo",
        done: false
      });
    });
  }, []);

Class (opens in a new tab)

如果用的是 class/有 prototype 生成的对象变量,需要声明和 immer 的兼容性

import { immerable } from "immer";
 
class Foo {
  [immerable] = true; // Option 1
 
  constructor() {
    this[immerable] = true; // Option 2
  }
}
 
Foo[immerable] = true; // Option 3