跳到主要内容

Immer 性能

egghead.io 第5课: 在 React 中利用 Immer 的结构共享
egghead.io 第7课: 如果没有语义变化,Immer 会使用原先的数据

这是一个关于 Immer 性能的 简单 benchmark 。该测试需要 50,000 个待办事项并更新其中的 5,000 个。 Freeze 表示状态树在生成后已被冻结。这是一种开发最佳实践,因为它可以防止开发人员意外修改状态树。

上面的数字没有反映一些东西,但实际上,Immer 有时比手写的 reducer 得多。这样做的原因是,Immer 会检测“无操作”状态变化,如果实际上没有任何变化,则返回原始状态,这可以避免很多重新渲染。众所周知,只需应用 immer 即可解决关键性能问题。

这些测试在 Node 10.16.3 上执行。使用 yarn test:perf 在本地重现它们。

performance.png

最重要的结论:

  • Immer with proxies 大约比手写 reducer 慢 2 到 3 倍(上面的测试用例是最坏的情况,请参阅 yarn test:perf 了解更多测试情况)。这在实践中可以忽略不计。
  • Immer 的速度大致与 ImmutableJS 一样快。但是,immutableJS + toJS 明确了后期往往需要付出的代价;将 immutableJS 对象转换回普通对象,以便将它们传递给组件或者进行序列化操作在网络中传输......(还有将从服务器接收到的数据转换为不可变 JS 的前期成本)
  • 生成 patches 不会显著减慢 immer
  • ES5 后备实现的速度大约比代理实现慢两倍,在某些情况下更糟。

性能提示

预冻结数据

当向 Immer producer 中的状态树添加大型数据集时(例如从 JSON 端点接收的数据),可以在首先添加的数据的根上调用 freeze(json) ,来浅冻结它。这将允许 Immer 更快地将新数据添加到树中,因为它将避免递归扫描和冻结新数据的需要。

您可以随时选择退出

immer 在任何地方都是可选的,因此手动编写性能非常苛刻的 reducers ,并将 immer 用于所有普通的的 reducers 是非常好的。即使在 producer 内部,您也可以通过使用 originalcurrent 函数来选择退出 Immer 的某些部分逻辑,并对纯 JavaScript 对象执行一些操作。

对于昂贵的搜索操作,从原始 state 读取,而不是 draft

Immer 会将您在 draft 中读取的任何内容也递归地转换为 draft。如果您对涉及大量读取操作的 draft 进行昂贵的无副作用操作,例如在非常大的数组中使用 find(Index) 查找索引,您可以通过首先进行搜索,并且只在知道索引后调用 produce 来加快速度。这样可以阻止 Immer 将在 draft 中搜索到的所有内容都进行转换。或者,使用 original(someDraft) 对 draft 的原始值执行搜索,这归结为同样的事情。

将 produce 拉到尽可能远的地方

始终尝试将 produce “向上”拉动,例如 for (let x of y) produce(base, d => d.push(x))produce(base, d => { for (let x of y) ) d.push(x)}) 慢得多