immer 的主旨是通过更简单的方式使用 immutable 不可变数据,它是基于 copy-on-write 机制的(如果资源重复未被修改,则无需创建新的资源,资源会在副本与原始位置之间共享;修改则必须创建一个副本;通过这个机制 复制操作会被推迟到第一次写入时进行,显著的减少资源消耗,同时资源修改操作增加少量开销)
immer 中有如下三种数据概念
produce(baseState, draftState => {})
用于包装数据处理,并生产新数据 const baseState = {
obj: { b: 'xiaobai ' },
arr: [{ name: 'xiaobai', age: 20 }, { name: '小黑' }],
};
const nextState = produce(baseState, (draftState) => {
// 直接操作 draftState 即可,不需要返回值
draftState.arr.push({ name: 'ak' });
console.log(draftState); // Proxy { <target>: {…}, <handler>: {…} }
});
console.log(
nextState, // 返回的是普通对象
baseState,
nextState === baseState, // false 因为有值改变
nextState.obj === baseState.obj, // true 因为 baseState.obj 没有改变
nextState.arr === baseState.arr, // false
);
produce(draftState => {})(baseState)
用于创建一个 producer(预绑定生产者)预绑定生产者 producer 可以接收一个 baseState 参数来生成 nextState
const baseState = {
obj: { b: 'xiaobai ' },
arr: [{ name: 'xiaobai', age: 20 }, { name: '小黑' }],
};
const baseState2 = {
obj: { b: 'bbb' },
arr: [{ name: 'xiaobai', age: 20 }, { name: '小白' }],
};
const producer = produce((draftState: typeof baseState) => {
draftState.arr.push({ name: 'ak' });
});
const nextState = producer(baseState);
const nextState2 = producer(baseState2);
console.log(nextState, nextState2);
produce((draftState, ...args) => {}, initialState)(baseState, ...args)
producer 可以接收更多的参数, 以及初始数据 const baseState = {
obj: { b: 'xiaobai ' },
arr: [{ name: 'xiaobai', age: 20 }, { name: '小黑' }],
};
const baseState2 = {
obj: { b: 'bbb' },
arr: [{ name: 'xiaobai', age: 20 }, { name: '小白' }],
};
const producer = produce(
(draftState: typeof baseState, data?: { name: string }) => {
if (data) draftState.arr.push(data);
},
{ obj: { b: '' }, arr: [] }, // initialState
);
const nextState = producer(); // 不传返回初始值
const nextState2 = producer(baseState2, { name: 'second' });
console.log(nextState, nextState2);
immer.current immer.original
用于在 produce 中提取 draft 的当前值或者原始传入值 (注意:操作昂贵,一般只用于测试)const base = {
x: 0
}
const next = produce(base, draft => {
draft.x++
const orig = original(draft)
const copy = current(draft)
console.log(orig.x)
console.log(copy.x)
setTimeout(() => {
// this will execute after the produce has finised!
console.log(orig.x)
console.log(copy.x)
}, 100)
draft.x++
console.log(draft.x)
})
console.log(next.x)
// This will print
// 0 (orig.x)
// 1 (copy.x)
// 2 (draft.x)
// 2 (next.x)
// 0 (after timeout, orig.x)
// 1 (after timeout, copy.x)
// 不使用 immer
const { remote } = this.state;
const { cache, data } = remote;
// 需要一层层解构
this.setState({ remote: { ...remote, cache: [...cache, data] } });
// 使用 immer
this.setState(
// import {Draft} from 'immer'
produce((state: Draft<State>) => {
// 直接操作数据即可
state.remote.cache.push(state.remote.data);
}),
);
// 或者
this.setState(
produce<State>((state) => {
// 直接操作数据即可
state.remote.cache.push(state.remote.data);
}),
);
// 不使用 immer
function counterReducer(state = { counter: 0 }, action: { type: string }) {
switch (action.type) {
case 'counter/incremented':
// 需要 return 一个新的 state
return { counter: state.counter + 1 };
case 'counter/decremented':
return { counter: state.counter - 1 };
default:
return state;
}
}
// 使用 immer 后
const INITIAL_DATA = { counter: 0 };
const counterReducer = produce(
(state: typeof INITIAL_DATA, action: { type: string }) => {
switch (action.type) {
case 'counter/incremented':
state.counter += 1;
break;
case 'counter/decremented':
state.counter -= 1;
break;
default:
}
},
INITIAL_DATA,
);
一般来讲使用 immer 的 reducer 不需要 return 任何内容,如果一定要 return 可以查看 Returning new data from producers
需要特殊说明的是,如果需要 return 一个 undefined 该怎么操作?,直接 return undefined 是不行的,者会被认为是 return draft,需要按照如下操作
import produce, {nothing} from "immer"
const state = {
hello: "world"
}
produce(state, draft => nothing) // 相当于将 state 置为 undefined
- Redux 的版本到 4.x.x 以后以及趋于稳定,变更越来越少,但是基础版的 Api 使用起来比较繁琐,所以 Redux 团队又推出了一个封装版 Redux Toolkit , 这个封装版,是 redux 与 immer 的结合体。
如果想要使用 封装版请查看 Redux Toolkit