Mutative
Mutative - 一个用于高效不可变更新的 JavaScript 库,比朴素的自定义 reducer 快 2-6 倍,比 Immer 快 10 倍以上。
为什么 Mutative 比展开运算符(朴素的自定义 reducer)更快?
展开运算符存在性能陷阱,以下文章中有详细说明:
而 Mutative 的优化重点在于浅拷贝优化、更完整的惰性草稿、最终化过程优化等。
动机
手动编写不可变更新通常很困难,容易出错且繁琐。Immer 帮助我们使用“可变”逻辑编写更简单的不可变更新。
但其性能问题导致了运行时性能开销。Immer 必须默认启用 auto-freeze(如果禁用 auto-freeze,性能会更差),使用 Immer 的此类不可变状态并不常见。在跨进程、远程数据传输等场景中,这些不可变数据必须不断被冻结。
还有更多可以改进的地方,例如更好的类型推断、非侵入式标记、支持更多类型的不可变性、更安全的不可变性、更多边缘情况,等等。
这就是创建 Mutative 的原因。
可变式与朴素手工编写 Reducer 的性能
按对象划分的可变式与 Reducer 基准测试:
- 朴素手工编写的 reducer
// baseState type: Record<string, { value: number }>
const state = {
...baseState,
key0: {
...baseState.key0,
value: i,
},
};
- 可变
const state = create(baseState, (draft) => {
draft.key0.value = i;
});

更新 1K-100K 项对象所需的测量时间(秒),越低越好(查看源)。
Mutative 在更新不可变对象时,比朴素的手工编写 reducer 快至 2 倍。
Mutative 与 Reducer 按数组进行的基准测试:
- 朴素的手工编写 reducer
// baseState type: { value: number }[]
// slower 6x than Mutative
const state = [
{ ...baseState[0], value: i },
...baseState.slice(1, baseState.length),
];
// slower 2.5x than Mutative
// const state = baseState.map((item, index) =>
// index === 0 ? { ...item, value: i } : item
// );
// same performance as Mutative
// const state = [...baseState];
// state[0] = { ...baseState[0], value: i };
实际差异取决于您使用的哪种展开操作语法。
- 可变
const state = create(baseState, (draft) => {
draft[0].value = i;
});

更新 1K-100K 项数组的耗时(秒),越低越好(查看源)。
Mutative 在更新不可变数组方面,比朴素的手工编写 reducer 快至 6 倍。
Mutative 与 Immer 性能对比
Mutative 通过了 Immer 的所有测试用例。
测量(ops/sec)更新 50K 个数组和 1K 个对象,数值越大越好(查看源码)。[Mutative v1.3.0 对比 Immer v10.1.3]

Naive handcrafted reducer - No Freeze x 4,777 ops/sec ±1.06% (94 runs sampled)
Mutative - No Freeze x 6,783 ops/sec ±0.71% (96 runs sampled)
Immer - No Freeze x 5.72 ops/sec ±0.39% (19 runs sampled)
Mutative - Freeze x 1,069 ops/sec ±0.75% (97 runs sampled)
Immer - Freeze x 392 ops/sec ±0.66% (92 runs sampled)
Mutative - Patches and No Freeze x 1,006 ops/sec ±1.73% (95 runs sampled)
Immer - Patches and No Freeze x 5.73 ops/sec ±0.16% (19 runs sampled)
Mutative - Patches and Freeze x 548 ops/sec ±1.06% (94 runs sampled)
Immer - Patches and Freeze x 287 ops/sec ±0.84% (93 runs sampled)
The fastest method is Mutative - No Freeze
运行 yarn benchmark 以测量性能。
OS: macOS 14.7, CPU: Apple M1 Max, Node.js: v22.11.0
Immer 依赖于启用 auto-freeze,如果 auto-freeze 被禁用,Immer 的性能将出现大幅下降,而 Mutative 将获得巨大的性能优势,特别是在处理大型数据结构时,其性能优势将超过 50 倍。
因此,如果你正在使用 Immer,为了性能你必须启用 auto-freeze。Mutative 默认禁用了 auto-freeze。在两者的默认配置下,我们可以看到 Mutative (6,783 ops/sec) 和 Immer (392 ops/sec) 之间 17 倍的性能差距。
总体而言,在更多性能测试场景中,Mutative 相比 Immer 具有巨大的性能优势。运行 yarn performance 以在本地获取所有性能结果。
特性与优势
- Mutation 实现不可变更新 - 支持对象、数组、Set 和 Map 的不可变数据结构。
- 高性能 - 默认情况下比 immer 快 10 倍,甚至比朴素的手写 reducer 更快。
- 可选的冻结状态 - 默认不冻结不可变数据。
- 支持 JSON Patch - 完全符合 JSON Patch 规范。
- 自定义浅拷贝 - 支持更多类型的不可变数据。
- 支持对不可变和可变数据进行标记 - 允许非侵入式标记。
- 严格模式下更安全的可变数据访问 - 带来更安全的不可变更新。
- 支持 reducer - 支持 reducer 函数及其他任何不可变状态库。
Mutative 与 Immer 的区别
| Mutative | Immer | |
|---|---|---|
| 自定义浅拷贝 | ✅ | ❌ |
| 严格模式 | ✅ | ❌ |
| 默认不冻结数据 | ✅ | ❌ |
| 非侵入式标记 | ✅ | ❌ |
| 完全冻结数据 | ✅ | ❌ |
| 非全局配置 | ✅ | ❌ |
| 异步 draft 函数 | ✅ | ❌ |
| 完全兼容 JSON Patch 规范 | ✅ | ❌ |
| 新的 Set 方法 (Mutative v1.1.0+) | ✅ | ❌ |
Mutative 比 Immer 拥有更少的 bug,例如意外的草稿逃逸,查看详情。
安装
Yarn
yarn add mutative
NPM
npm install mutative
CDN
- Unpkg:
<script src="https://unpkg.com/mutative"></script> - JSDelivr:
<script src="https://cdn.jsdelivr.net/npm/mutative"></script>
用法
import { create } from 'mutative';
const baseState = {
foo: 'bar',
list: [{ text: 'coding' }],
};
const state = create(baseState, (draft) => {
draft.list.push({ text: 'learning' });
});
expect(state).not.toBe(baseState);
expect(state.list).not.toBe(baseState.list);
create(baseState, (draft) => void, options?: Options): newState
create() 的第一个参数是基础状态。Mutative 会对其进行草稿化,并将其传递给草稿函数的参数,然后执行草稿变更,直到草稿函数结束,随后 Mutative 会将其最终化并生成新状态。
通过 设置 options] 使用 create() 来访问更高级的功能。
APIs
create()apply()current()original()unsafe()isDraft()isDraftable()rawReturn()makeCreator()markSimpleObject()
create()
使用 create() 进行 draft mutation 以获取新状态,该操作也支持柯里化。
import { create } from 'mutative';
const baseState = {
foo: 'bar',
list: [{ text: 'todo' }],
};
const state = create(baseState, (draft) => {
draft.foo = 'foobar';
draft.list.push({ text: 'learning' });
});
在这个基本示例中,对草稿的更改在草稿回调内是“可变”的,并且 create() 最终在一个新的不可变状态下执行。
create(state, fn, options)
然后 options 是可选的。
-
strict -
boolean,默认值为 false。在严格模式下禁止访问非可草稿化的值(除非使用 unsafe())。
当启用严格模式时,可变数据只能通过
unsafe()访问。建议在开发模式下启用
strict,在生产模式下禁用strict。 这将确保安全的显式返回,同时保持生产构建的良好性能。如果返回的值不混合任何当前草稿或是undefined,则使用 rawReturn()。如果您希望在开发构建中默认启用严格模式,并在生产环境中关闭它,可以使用
strict: process.env.NODE_ENV !== 'production'。 -
enablePatches -
boolean | { pathAsArray?: boolean; arrayLengthAssignment?: boolean; },默认值为 false。启用 patch,并返回 patches/inversePatches。
如果您需要更详细地设置生成的 patch 的形状,则可以设置
pathAsArray和arrayLengthAssignment。pathAsArray的默认值是true,如果为true,路径将是一个数组,否则是一个字符串;arrayLengthAssignment的默认值是true,如果为true,数组长度将包含在 patches 中,否则不包含数组长度(注意:如果arrayLengthAssignment为false,则完全兼容 JSON Patch 规范,但可能会有额外的性能损失),查看相关讨论。 -
enableAutoFreeze -
boolean,默认值为 false。启用 autoFreeze,并返回冻结状态,仅在
development模式下启用循环引用检查。 -
mark -
(target) => ('mutable'|'immutable'|function) | (target) => ('mutable'|'immutable'|function)[]设置一个标记以确定值是可变还是实例是不可变的,它还可以返回一个浅拷贝函数(
AutoFreeze和Patches都应禁用,某些补丁操作可能并不等价)。 当标记函数为 (target) => 'immutable' 时,意味着状态结构中的所有对象都是不可变的。在这种特定情况下,您可以完全开启AutoFreeze和Patches。mark支持多个标记,标记按顺序执行,第一个返回值的标记将被使用。 当对象树节点被mark函数标记为mutable时,其所有子节点也不会被 Mutative 草稿化,并将保留其原始值。
create() - 柯里化
- 创建
draft
const [draft, finalize] = create(baseState);
draft.foobar.bar = 'baz';
const state = finalize();
支持集选项,例如
const [draft, finalize] = create(baseState, { enableAutoFreeze: true });
- 创建
producer
const produce = create((draft) => {
draft.foobar.bar = 'baz';
});
const state = produce(baseState);
还支持设置诸如
const produce = create((draft) => {}, { enableAutoFreeze: true });之类的选项
apply()
使用 apply() 来应用补丁以获取新状态。
import { create, apply } from 'mutative';
const baseState = {
foo: 'bar',
list: [{ text: 'todo' }],
};
const [state, patches, inversePatches] = create(
baseState,
(draft) => {
draft.foo = 'foobar';
draft.list.push({ text: 'learning' });
},
{
enablePatches: true,
}
);
const nextState = apply(baseState, patches);
expect(nextState).toEqual(state);
const prevState = apply(state, inversePatches);
expect(prevState).toEqual(baseState);
apply(state, patches, options)
options 参数是可选的,支持两种类型的配置:
-
不可变选项(类似于创建选项,但不包含
enablePatches):strict-boolean,在严格模式下禁止访问非可草稿化的值enableAutoFreeze-boolean,启用 autoFreeze 并返回冻结状态mark- 用于确定值是否为可变/不可变的标记函数
const baseState = { foo: { bar: 'test' } };
// This will create a new state.
const result = apply(baseState, [
{
op: 'replace',
path: ['foo', 'bar'],
value: 'test2',
},
]);
expect(baseState).not.toEqual({ foo: { bar: 'test2' } });
expect(result).toEqual({ foo: { bar: 'test2' } });
- 可变选项(Mutative v1.2.0+):
mutable-boolean,如果为 true,状态将被直接修改,而不是创建新状态
可变选项示例:
const baseState = { foo: { bar: 'test' } };
// This will modify baseState directly
apply(
baseState,
[
{
op: 'replace',
path: ['foo', 'bar'],
value: 'test2',
},
],
{
mutable: true,
}
);
expect(baseState).toEqual({ foo: { bar: 'test2' } });
⚠️注意:mutable 选项不能与其他选项组合使用。使用 mutable 选项时,apply() 将返回 void 而不是新的状态。
current()
从草稿中获取当前值。
- 对于任何子节点已被修改的草稿,每次执行 current() 获得的状态都将是一个新的引用对象。
- 对于没有子节点被修改的草稿,执行 current() 始终返回原始状态。
建议在执行只读操作时,尽量减少执行 current() 的次数,理想情况下仅执行一次。
const state = create({ a: { b: { c: 1 } }, d: { f: 1 } }, (draft) => {
draft.a.b.c = 2;
expect(current(draft.a)).toEqual({ b: { c: 2 } });
// The node `a` has been modified.
expect(current(draft.a) === current(draft.a)).toBeFalsy();
// The node `d` has not been modified.
expect(current(draft.d) === current(draft.d)).toBeTruthy();
});
original()
从草稿中获取原始值。
const baseState = {
foo: 'bar',
list: [{ text: 'todo' }],
};
const state = create(baseState, (draft) => {
draft.foo = 'foobar';
draft.list.push({ text: 'learning' });
expect(original(draft.list)).toEqual([{ text: 'todo' }]);
});
unsafe()
当启用严格模式时,可变数据只能通过 unsafe() 访问。
const baseState = {
list: [],
date: new Date(),
};
const state = create(
baseState,
(draft) => {
unsafe(() => {
draft.date.setFullYear(2000);
});
// or return the mutable data:
// const date = unsafe(() => draft.date);
},
{
strict: true,
}
);
如果你希望在开发构建中默认启用严格模式,并在生产环境中将其关闭,可以使用
strict: process.env.NODE_ENV !== 'production'。
isDraft()
检查某个值是否为草稿。
const baseState = {
date: new Date(),
list: [{ text: 'todo' }],
};
const state = create(baseState, (draft) => {
expect(isDraft(draft.date)).toBeFalsy();
expect(isDraft(draft.list)).toBeTruthy();
});
isDraftable()
检查某个值是否可草稿化
const baseState = {
date: new Date(),
list: [{ text: 'todo' }],
};
expect(isDraftable(baseState.date)).toBeFalsy();
expect(isDraftable(baseState.list)).toBeTruthy();
您可以设置一个标记来确定值是否可草稿化,并且标记函数应与传入的
create()标记选项相同。
rawReturn()
对于不包含任何草稿的返回值,你可以使用 rawReturn() 来包装该返回值以提升性能。它确保该返回值仅被显式返回。
const baseState = { id: 'test' };
const state = create(baseState as { id: string } | undefined, (draft) => {
return rawReturn(undefined);
});
expect(state).toBe(undefined);
如果返回值混合了草稿,则不应使用
rawReturn()。
const baseState = { a: 1, b: { c: 1 } };
const state = create(baseState, (draft) => {
if (draft.b.c === 1) {
return {
...draft,
a: 2,
};
}
});
expect(state).toEqual({ a: 2, b: { c: 1 } });
expect(isDraft(state.b)).toBeFalsy();
如果你使用 rawReturn(),我们建议你在开发中启用 strict 模式。
const baseState = { a: 1, b: { c: 1 } };
const state = create(
baseState,
(draft) => {
if (draft.b.c === 1) {
return rawReturn({
...draft,
a: 2,
});
}
},
{
strict: true,
}
);
// it will warn `The return value contains drafts, please don't use 'rawReturn()' to wrap the return value.` in strict mode.
expect(state).toEqual({ a: 2, b: { c: 1 } });
expect(isDraft(state.b)).toBeFalsy();
makeCreator()
makeCreator() 仅接受 options 作为第一个参数,从而生成一个自定义的 create() 函数。
const baseState = {
foo: {
bar: 'str',
},
};
const create = makeCreator({
enablePatches: true,
});
const [state, patches, inversePatches] = create(baseState, (draft) => {
draft.foo.bar = 'new str';
});
markSimpleObject()
markSimpleObject() 是一个标记函数,用于将所有对象标记为不可变。
const baseState = {
foo: {
bar: 'str',
},
simpleObject: Object.create(null),
};
const state = create(
baseState,
(draft) => {
draft.foo.bar = 'new str';
draft.simpleObject.a = 'a';
},
{
mark: markSimpleObject,
}
);
expect(state.simpleObject).not.toBe(baseState.simpleObject);
使用 TypeScript
castDraft()castImmutable()castMutable()Draft<T>Immutable<T>PatchesPatchOptions<O, F>
与 React 集成
- use-mutative - 一种比使用展开运算符的 useState 快 2-6 倍的替代方案
- use-travel - 一个用于状态时间旅行的 React Hook,具备撤销、重做、重置和归档功能。
- zustand-mutative - 一个用于 Zustand 的 Mutative 中间件,可提升不可变状态更新的效率。
常见问题
- 我已经在用 Immer,能否平滑迁移到 Mutative?
可以。除非你需要兼容 Internet Explorer,否则 Mutative 支持几乎所有 Immer 的功能,你可以轻松地从 Immer 迁移到 Mutative。
对于不支持 Proxy 的 React Native,迁移也是不可能的。React Native 在重构期间使用了一个新的 JS 引擎 - Hermes,它(如果 < v0.59 或在 React Native < v0.64 上使用 Hermes 引擎时)不支持 Android 上的 Proxy,但 React Native v0.64 配合 Hermes 引擎支持 Proxy。
- Mutative 能否与 Redux 集成?
可以。Mutative 支持 reducer 的返回值,并且 redux-toolkit 正在考虑支持 可配置的 produce()。
- Mutative 是否支持共享引用?
是的,Mutative 支持共享引用,但通往共享对象的每条路径都会获得其自己独立的草稿。对一条路径的修改不会自动反映到其他路径中。如果你希望在结果中保留共享引用,你必须显式地分配它们(例如,draft.b = draft.a)。阅读更多细节。
从 Immer 迁移到 Mutative
mutative-compat - Mutative 包装器,提供完整的 Immer API 兼容性,您可以使用它快速从 Immer 迁移到 Mutative。
produce()->create()
Mutative 的自动冻结选项默认禁用,Immer 的自动冻结选项默认启用(如果禁用,Immer 的性能将出现更大幅度的下降)。
您需要检查自动冻结是否会对您的项目产生影响。如果它依赖于自动冻结,您可以在 Mutative 中自行启用。
import produce from 'immer';
const nextState = produce(baseState, (draft) => {
draft[1].done = true;
draft.push({ title: 'something' });
});
使用 Mutative
import { create } from 'mutative';
const nextState = create(baseState, (draft) => {
draft[1].done = true;
draft.push({ title: 'something' });
});
Patches
import { produceWithPatches, applyPatches } from 'immer';
enablePatches();
const baseState = {
age: 33,
};
const [nextState, patches, inversePatches] = produceWithPatches(
baseState,
(draft) => {
draft.age++;
}
);
const state = applyPatches(nextState, inversePatches);
expect(state).toEqual(baseState);
使用 Mutative
import { create, apply } from 'mutative';
const baseState = {
age: 33,
};
const [nextState, patches, inversePatches] = create(
baseState,
(draft) => {
draft.age++;
},
{
enablePatches: true,
}
);
const state = apply(nextState, inversePatches);
expect(state).toEqual(baseState);
- 返回
undefined
import produce, { nothing } from 'immer';
const nextState = produce(baseState, (draft) => {
return nothing;
});
使用 Mutative
import { create, rawReturn } from 'mutative';
const nextState = create(baseState, (draft) => {
return rawReturn(undefined);
});
贡献
Mutative 的目标是提供高效且不可变的更新。重点在于性能优化以及提供更好的 API 以获得更好的开发体验。我们仍在持续改进,欢迎提交可能有助于 Mutative 的 PR。
开发工作流:
- 克隆 Mutative 仓库。
- 运行
yarn install以安装所有依赖项。 - 运行
yarn prettier以格式化代码。 yarn test --watch运行交互式测试监视器。- 运行
yarn commit以创建 git 提交。
许可证
Mutative 采用 MIT 许可证。

