跳至主内容区

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()

该插件通过避免迭代期间为每个元素创建代理,优化了 filter、find、some、every 和 slice 等数组操作。若不启用插件,迭代包含 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 内部,也可通过 original 或 current 工具在特定逻辑中退出 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)}) 呈指数级更慢。