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