前言
不可变对象是单向数据流中重要的环节,可以避免数据双向修改,规范开发流程。React redux、flux、组件 state 中可以看到不可变对象的身影。javascript 中实现不可变对象的常见方法是对象展开运算符,如下代码:
const abc = {
aa: 'bb',
bb: 'cc',
};
const def = {...abc};
console.log(abc === def); // false
javascript 原生实现的好处是代码简单,但问题也很明显,每次都需要重复拷贝对象,当对象很大时,拷贝的开销过大,影响执行效率。我们希望每次改变对象时指修改相关属性指针和父对象指针,避免无修改属性的拷贝,如下图:
immutable 和 immer 这两个包可以实现我们想要的需求。
immutable 与 immer 对比
使用 immutable 可以很方便地处理不可变数据(或不可变数据集),一个复杂系统中数据不可变是很重要的一部分,比如为了实现严格的单向数据流,避免直接使用 abc.def = 123 这样的代码修改对象。 js 语法实现不可变数据通常用对象展开运算符实现:
const newState = {...oldState, def: 123};
在对象相对简单时这是很简洁的方法,但对象特别大时每次使用对象展开运算符都需要执行繁重的对象拷贝工作,影响程序执行效率。使用 immutable 可以轻松解决这个问题,immutable 只改变相关变动的指针和相应的父指针,避免无关数据的重复拷贝,大大提高程序执行效率。简单使用:
const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
如果你使用 React 进行 Web 开发,相信你对 immutable 已经很熟悉了,但它带来好处的同时也带来一些问题,比如:代码大量耦合不利职责分离,使用字符串引用不利于代码维护等。这时你可以考虑类似功能的包:immer
immer 解决了immutable 的痛点问题,按需使用,可以很好解耦相关功能,项目中无需大量依赖 immutable。它也没有定义新的 API,使用的集合类型都是 javascript 自带的类型,有 typescript 强类型推断,无需使用字符串,整体包非常小(3KB)。
可以直接操作对象,无需使用对象展开运算符,如下代码:
import produce from "immer"
const baseState = [
{
todo: "Learn typescript",
done: true
},
{
todo: "Try immer",
done: false
}
]
const nextState = produce(baseState, draftState => {
draftState.push({todo: "Tweet about it"})
draftState[1].done = true
})
迁移技巧
如果你还未使用过 immutable 和 immer, reducer 代码使用对象展开运算符实现不可变的 State,可以尝试一下技巧,做到代码无缝迁移,不影响功能,提高代码效率。
新建一个 help 函数:
import produce from 'immer';
export function createReducer<State, Payload>(
cases: { [key: string]: (s: State, action: ReduxAction<Payload>) => State | any } = {},
defaultState: State
) {
return (
state = defaultState, action: ReduxAction<Payload>
) => produce(state, (draft: State) => {
if (action && action.type && cases[action.type] instanceof Function) {
cases[action.type](draft, action);
}
});
}
修改 reducer 代码:
interface State {
abc: string;
}
const defaultState = {
abc: 'def';
};
export default createReducer<State, any>({
UPDATE_ABC(state, action) {
state.abc = 'haha';
}
}, defaultState)
这样即可实现数据不可变的同时,按普通 js 修改对象的逻辑组织代码,避免大量对象展开语法,使得代码难以维护。
如果原先代码使用对象展开运算符,上述代码(createReducer 函数)也不会影响正常功能,做到无缝迁移,按需修改即可。
本文经作者授权转载,原文作者:HD Superman,原文链接:使用 immer 优化 redux 代码(无缝迁移)