Skip to main content

immer

简介

immer 实现了 JS 的数据不可变状态。核心实现是利用 ES6 的 Proxy

  • 文档
  • github
  • CDN
    • <script src="https://unpkg.com/immer"></script>
    • <script src="https://cdn.jsdelivr.net/npm/immer"></script>

现象:原始数据被修改。

解决对象类型被修改的办法,一般是使用深拷贝来解决。

先定义一个原始对象,下面有 4 种情况都可以改变原始对象 obj

const obj = {
a: {
b: [1, 2, 3]
}
};

// let p1 = obj;
// p1.a = 1;

// let p2 = { ...obj };
// p2.a.b = 2;

// let p3 = obj;
// p3.a.b.push(4);

// function fn(param) {
// param.a = 1;
// return param;
// }
// let p4 = fn(obj);

console.log(obj);

immer.js

pnpm add immer

可以先看看 immer 里都有哪些内容:API

import * as immer from 'immer';

console.log(immer);

现在解决上述问题:

import { produce } from 'immer';

const obj = {
a: {
b: [1, 2, 3]
}
};

// 解决 p1 p2
let p1 = produce(obj, draft => {
draft.a = 2;
});
console.log(p1); // { a: 2 }

// 解决 p3
let p3 = produce(obj, draft => {
draft.a.b.push(4);
});
console.log(p3); // { a: { b: [ 1, 2, 3, 4 ] } }

// 解决 p4
function fn(param) {
return produce(param, draft => {
draft.a = 1;
});
}
const p4 = fn(obj);
console.log(p4); // { a: 1 }

immer 相关概念

  • currentState 被操作对象的最初状态
  • draftState 根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的任何修改都将被记录并用于生成 nextState 。在此过程中,currentState 将不受影响
  • nextState 根据 draftState 生成的最终状态
  • produce 生产,用来生成 nextState 或 producer 的函数
  • producer 生产者,通过 produce 生成,用来生产 nextState ,每次执行相同的操作
  • recipe 生产机器,用来操作 draftState 的函数

produce 语法:produce(currentState, recipe: (draftState) => void): nextState

produce 和柯里化

import { produce } from 'immer';

const baseState = [
{ id: 'JavaScript', title: 'Learn TypeScript', done: true },
{ id: 'Immer', title: 'Try Immer', done: false }
];

function toggleTodo(state, id) {
return produce(state, draft => {
const todo = draft.find(todo => todo.id === id);
todo.done = !todo.done;
});
}

const nextState = toggleTodo(baseState, 'Immer');

produce 柯里化:

const toggleTodo = produce((draft, id) => {
const todo = draft.find(todo => todo.id === id);
todo.done = !todo.done;
});

更新模式

更新模式,更删改

  • 更新对象
  • 更新数组
  • 嵌套数据结构

immutable-js

immutable-js,也是一个操作不可变数据的库,但是上手复杂

use-immer

use-immer和 useState 很相似,返回一个状态和一个更新函数。如果感觉更新数组和对象很烦琐、嵌套层级很深,可以使用 immer 编写简洁的代码。

pnpm add immer use-immer

1. 管理对象或数组类型的状态

示例:有一个输入框,在输入内容时,更新 b.c 的值

使用 useState
import { useState } from 'react';

function App() {
const [info, setInfo] = useState({
a: 'react',
b: {
c: 'hello',
d: 'world'
}
});

function handleChange(e) {
const value = e.target.value;
setInfo(data => ({
...data,
b: {
...data.b,
c: value
}
}));
}

return (
<>
<h1>Hello {info.a}</h1>
<h1>
{info.b.c} {info.b.d}
</h1>
<input onChange={handleChange} value={info.b.c} />
</>
);
}

export default App;
使用 immer
import { useImmer } from 'use-immer';

function App() {
const [info, setInfo] = useImmer({
a: 'react',
b: {
c: 'hello',
d: 'world'
}
});

function handleChange(e) {
const value = e.target.value;
setInfo(draft => {
draft.b.c = value;
});
}

return (
<>
<h1>Hello {info.a}</h1>
<h1>
{info.b.c} {info.b.d}
</h1>
<input onChange={handleChange} value={info.b.c} />
</>
);
}

export default App;

2. 管理基础类型的状态

import React from 'react';
import { useImmer } from 'use-immer';

function BirthDayCelebrator() {
const [age, setAge] = useImmer(20);

function birthDay(event) {
setAge(age + 1);
alert(`Happy birthday #${age} Anon! hope you good`);
}

return (
<div>
<button onClick={birthDay}>It is my birthday</button>
</div>
);
}

useImmerReducer

基于 useReducer 的 hook

import React from 'react';
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>
</>
);
}