You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
当向 Immer producer 中的状态树添加大型数据集时(例如从 JSON 端接收的数据),可以在首先添加的数据的最外层调用 freeze(json) 来浅冻结它。这将允许 Immer 更快地将新数据添加到树中,因为它将避免递归扫描和冻结新数据的需要。
可以随时选择退出
immer 在任何地方都是可选的,因此手动编写性能非常苛刻的 reducers ,并将 immer 用于所有普通的的 reducers 是非常好的。即使在 producer 内部,您也可以通过使用 original 或 current 函数来选择退出 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)}) 慢得多
theme: fancy
一、前言
Immer 是 mobx 的作者写的一个 immutable 库,核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构,简单易用、体量小巧、设计巧妙,满足了我们对 JS 不可变数据结构的需求。
二、学习前提
阅读这篇文章需要以下知识储备:
三、历史背景
在 js 中,处理数据一直存在一个问题:
拷贝一个值的时候,如果这个值是引用类型(比如对象、数组),直接赋值给另一个变量的时候,会把值的引用也拷贝过去,在修改新变量的过程中,旧的变量也会被一起修改掉。
要解决这个问题,通常我们不会直接赋值,而是会选择使用深拷贝,比如
JSON.parse(JSON.stringify())
,再比如lodash
为我们提供的cloneDeep
方法……但是,深拷贝并不是十全十美的。
这个时候,
immer
诞生了!四、immer 功能介绍
基本思想是,使用 Immer,会将所有更改应用到临时 draft,它是 currentState 的代理。一旦你完成了所有的 mutations,Immer 将根据对 draft state 的 mutations 生成 nextState。这意味着你可以通过简单地修改数据来与数据交互,同时保留不可变数据的所有好处。
一个简单的比较示例
假设我们有上述基本状态,我们需要更新第二个 todo,并添加第三个。但是,我们不想改变原始的 baseState,我们也想避免深度克隆以保留第一个 todo
不使用 Immer
如果没有 Immer,我们将不得不小心地浅拷贝每层受我们更改影响的 state 结构
使用 Immer
使用 Immer,这个过程更加简单。我们可以利用
produce
函数,它将我们要更改的 state 作为第一个参数,对于第二个参数,我们传递一个名为 recipe 的函数,该函数传递一个draft
参数,我们可以对其应用直接的mutations
。一旦recipe
执行完成,这些mutations
被记录并用于产生下一个状态。produce
将负责所有必要的复制,并通过冻结数据来防止未来的意外修改。使用 Immer 就像拥有一个私人助理。助手拿一封信(当前状态)并给您一份副本(草稿)以记录更改。完成后,助手将接受您的草稿并为您生成真正不变的最终信件(下一个状态)。
第二个示例
如果有一个层级很深的对象,你在使用 redux 的时候,想在 reducer 中修改它的某个属性,但是根据 reduce 的原则,我们不能直接修改 state,而是必须返回一个新的 state
不使用 Immer
使用 Immer
好处
更新模式
在 Immer 之前,使用不可变数据意味着学习所有不可变的更新模式。
为了帮助“忘记”这些模式,这里概述了如何利用内置 JavaScript API 来更新对象和集合
更新对象
更新数组
嵌套数据结构
异步 producers & createDraft
允许从 recipe 返回 Promise 对象。或者使用 async / await。这对于长时间运行的进程非常有用,只有在 Promise 链解析后才生成新对象
注意,如果 producer 是异步的,produce 本身也会返回一个 promise。
例子:
请注意,draft 不应从异步程序中“泄露”并存储在其他地方。异步过程完成后,draft 仍将被释放
createDraft
和finishDraft
createDraft
和finishDraft
是两个底层函数,它们对于在 immer 之上构建抽象的库非常有用。避免了为了使用 draft 始终创建函数。相反,人们可以创建一个 draft,对其进行修改,并在未来的某个时间完成该 draft,在这种情况下,将产生下一个不可变状态。
例如,我们可以将上面的示例重写为:
性能提示
预冻结数据
当向 Immer producer 中的状态树添加大型数据集时(例如从 JSON 端接收的数据),可以在首先添加的数据的最外层调用
freeze(json)
来浅冻结它。这将允许 Immer 更快地将新数据添加到树中,因为它将避免递归扫描和冻结新数据的需要。可以随时选择退出
immer 在任何地方都是可选的,因此手动编写性能非常苛刻的 reducers ,并将 immer 用于所有普通的的 reducers 是非常好的。即使在 producer 内部,您也可以通过使用
original
或current
函数来选择退出 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)})
慢得多陷阱
不要重新分配 recipe 参数
永远不要重新分配
draft
参数(例如:draft = myNewState
)。相反,要么修改 draft,要么返回新状态。Immer 只支持单向树
Immer 假设您的状态是单向树。也就是说,任何对象都不应该在树中出现两次,也不应该有循环引用。从根到树的任何节点应该只有一条路径。
永远不要从 producer 那里显式返回
undefined
可以从 producers 返回值,但不能以这种方式返回
undefined
,因为它与根本不更新 draft 没有区别!不要修改特殊对象
Immer 不支持特殊对象 比如 window.location
只有有效的索引和长度可以在数组上改变
对于数组,只能改变数值属性和 length 属性。自定义属性不会保留在数组上。
只有来自 state 的数据会被 draft
请注意,来自闭包而不是来自基本 state 的数据将永远不会被 draft,即使数据已成为新 darft 的一部分
始终使用嵌套 producers 的结果
支持嵌套调用
produce
,但请注意produce
将始终产生新状态,因此即使将 draft 传递给嵌套 produce,内部 produce 所做的更改也不会在传递给它的 draft 中可见,只会反映在产生的输出中。换句话说,当使用嵌套 produce 时,您会得到 draft 的 draft,并且内部 produce 的结果会被合并回原始 draft(或返回)
错误示范:
正确示范:
Drafts 在引用上不相等
Immer 中的 draft 对象包装在
Proxy
中,因此您不能使用 == 或 === 来测试原始对象与其 draft 之间的相等性,相反,可以使用 original:如果可以的话,建议在
produce
函数之外执行比较,或者使用.id
之类的唯一标识符属性,以避免需要使用original
。The text was updated successfully, but these errors were encountered: