跳至主内容区

Immer 性能表现

[非官方测试版翻译]

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

egghead.io lesson 5: Leveraging Immer's structural sharing in React
egghead.io lesson 7: Immer will try to re-cycle data if there was no semantic change

这里提供了 Immer 的简单基准测试。该测试使用 50,000 个待办事项并更新其中 5,000 项。Freeze 表示状态树在生成后已被冻结。这是 开发 阶段的最佳实践,可防止开发者意外修改状态树。

上述数据未体现的是:实际上 Immer 有时明显 快于 手动编写的 reducer。原因在于 Immer 会检测 "无操作" 状态变更,若实际未发生更改则返回原始状态,从而避免大量重复渲染。已知案例中仅应用 Immer 就解决了关键性能问题。

这些测试在 Node 10.16.3 环境下执行。使用 yarn test:perf 可在本地复现结果。

性能图表

核心观察结论:

  • 使用 Proxy 的 Immer 比手写 reducer 慢约两到三倍(上述测试为最坏情况,更多测试见 yarn test:perf)。实际应用中可忽略不计。

  • Immer 与 ImmutableJS 速度相当。但 immutableJS + toJS 揭示了后续常见成本:需将 immutableJS 对象转回普通对象才能传递给组件、网络传输等(此外还存在将服务器数据转换为 immutable JS 的前期成本)。

  • 生成补丁不会显著降低 Immer 性能

  • ES5 降级方案比 Proxy 实现慢约两倍,某些情况更差。

性能优化技巧

启用数组方法插件

对于在 producer 中存在大量数组迭代的应用,请启用数组方法插件

import {enableArrayMethods} from "immer"
enableArrayMethods()

该插件通过避免迭代期间为每个元素创建代理,优化了 filterfindsomeeveryslice 等数组操作。若不启用插件,迭代包含 1000 个元素的数组将创建 1000+ 代理。启用插件后,回调函数接收基础值,且仅在实际发生变更的元素上创建代理。

使用宽松迭代提升性能

默认情况下,Immer 采用宽松迭代模式,仅处理可枚举的字符串属性。该模式比包含符号和不可枚举属性的严格迭代更快。大多数场景下默认设置即为最优方案:

import {setUseStrictIteration} from "immer"

// Default: false (loose iteration for better performance)
setUseStrictIteration(false)

仅在明确需要追踪符号或不可枚举属性时才启用严格迭代。

预先冻结数据

当在 Immer producer 中向状态树添加大型数据集(如来自 JSON 接口的数据)时,建议先对数据根节点调用 freeze(json) 进行 浅冻结。这能加速 Immer 将新数据加入状态树的过程,避免 递归 扫描和冻结新数据。

随时可选择性退出

请注意 Immer 全程均为可选方案,因此完全可手动编写高性能关键 reducer 而将 Immer 用于常规场景。即使在 producer 内部,也可通过 originalcurrent 工具在特定逻辑中退出 Immer 机制,直接在原生 JavaScript 对象上执行操作。

高开销搜索操作应读取原始状态而非草稿

Immer 会将草稿中读取的任何内容递归转为草稿。若需在草稿上执行高开销无副作用操作(如在超大数组中通过 find(Index) 查找索引),可先执行搜索确定索引后再调用 produce 函数来提速。这样可避免 Immer 将搜索内容转为草稿。替代方案是使用 original(someDraft) 在草稿的原始值上搜索,效果等同。

尽可能提升 produce 层级

始终尝试将 produce 提升到更高层级,例如 for (let x of y) produce(base, d => d.push(x))produce(base, d => { for (let x of y) d.push(x)}) 呈指数级更慢。