Immutable & Immer

05. 三月 2019 JavaScript, React 0

文章来源于我在组内给同事分享的 Immer 和 immutable 的使用和对比,总结一下。

两个库都是用来解决数据的Immutable 问题的。但是使用上有很大区别。

Shared mutable state is the root of all evil(共享的可变状态是万恶之源)

Immutable

优点

  1. Immutable 降低了 Mutable 带来的复杂度 可变(Mutable)数据耦合了 Time 和 Value 的概念,造成了数据很难被回溯。
  2. 节省内存 Immutable.js 使用了 Structure Sharing (结构共享) 会尽量复用内存。没有被引用的对象会被垃圾回收。

    上面 a 和 b 共享了没有变化的 filter 节点。

  3. Undo/Redo(撤销重写),Copy/Paste,甚至时间回溯这些功能做起来小菜一碟
  4. 并发安全 因为js 单线程,所以没啥卵用
  5. 拥抱函数式编程 Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。

函数式编程的本质:函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。

缺点

  1. 容易与原生对象混淆 虽然 Immutable.js 尽量尝试把 API 设计的原生对象类似,有的时候还是很难区别到底是 Immutable 对象还是原生对象,容易混淆操作。 Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 map.get('key') 而不是 map.keyarray.get(0) 而不是 array[0]。另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。
  2. 当使用外部库的时候,一般需要使用原生对象,也很容易忘记转换。

下面给出一些办法来避免类似问题发生:

  • 使用 Flow 或 TypeScript 这类有静态类型检查的工具
  • 约定变量命名规则:如所有 Immutable 类型对象以 $$ 开头。
  • 使用 Immutable.fromJS 而不是 Immutable.Map 或 Immutable.List 来创建对象,这样可以避免 Immutable 和原生对象间的混用

 

API

太多了,图上也不是很全

 

 

 

总结: 功能很好,就是太难用,侵入性太强。放弃!

 

 

Immer

 

常用API就一个produce,掌握之后就可以应对绝大多数业务场景

produce

几个基本概念

  • currentState 被操作对象的最初状态
  • draftState 根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的任何修改都将被记录并用于生成 nextState 。在此过程中,currentState 将不受影响
  • nextState 根据 draftState 的修改生成的最终状态
  • recipe 操作方法 用来操作 draftState 的函数

produce的第一种使用姿势

draftState 的修改都会反应到 nextState 上,而 Immer 使用的结构是共享的,nextState 在结构上又与 currentState 共享未修改的部分,类似于immutable gif的样子

自动冻结

 

Immer 还在内部做了一件很巧妙的事情,那就是通过 produce 生成的 nextState 是被冻结(freeze)的,(Immer 内部使用Object.freeze方法,只冻结 nextState 跟 currentState 相比修改的部分),这样,当直接修改 nextState 时,将会报错。 这使得 nextState 成为了真正的不可变数据。

produce的第二种使用姿势

produce(recipe: (draftState) => void | draftState)(currentState): nextState

传入的第一个参数是函数

高阶函数的写法,提前生成一个producer方法,当producer 方法被调用的时候,它会把第一个参数用作你希望改变的 currentState

正因为有这个操作,所以我们可以很方便使用到setState 中,因为 setState 有接收函数作为参数的能力。

注意recipe的返回值

  • recipe 没有返回值时:nextState 是根据 recipe 函数内的 draftState 生成的;

  • recipe 有返回值时:nextState 是根据 recipe 函数的返回值生成的

但是不能同时修改了draftState 也 return 一个新的state,否则会报错,提示你只能修改draft 或者 return value;

Patches 补丁

Patches 可以在recipe执行期间记录所有操作,方便去做一些数据回滚或者是数据跟踪调试啥的。

patch 对象长这个模样

语法

新API applyPatches

不允许跨级回滚

createDraft、finishDraft

 

Immer 完败 Immutable,用immer 得永生。


发表评论

电子邮件地址不会被公开。