使用Immutable js的一点体会

3,923 阅读4分钟

项目中一直在使用Immutable js,最开始使用Immutable js,我的内心是抗拒的,简单的对象操作整得那么复杂,有必要吗。随着后面开发对数据操作非常频繁的项目后,渐渐发现了Immutable的闪光点; 现在就总结下使用Immutable时的一些体会;

一:什么是 Immutable Data

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。 Immutable 实现的原理是 Persistent Data >Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享;

通过阅读源码,知道,Immutable js 支持MapListSetCollectionOrderedMapStackOrderedSetRecordRangeRepeat九种数据结构;

最重要的也是经常用到的就是前三个;

Map:键值对集合,对应于 Object,ES6 也有专门的 Map 对象

List:有序可重复的列表,对应于 Array

Set:无序且不可重复的列表

二:为什么要用ImmutableImmutable带来的好处有哪些?

第一点,在没有使用Immutable之前操作store对象型数据的时候在不想修改原数据的时候通常的做法是复制一份,在复制的数据上做更新修改操作;但是每次deep-copy都要把整个对象递归的复制一份,如果遇到很复杂的对象型数据时,这样性能会很差;

而现在使用了Immutable,当我们发生一个set操作的时候,Immutable.js只修改这个节点和受它影响的父节点,其它节点则进行共享,可以大大提高性能;这里在网上找到一张动图:

第二点, 在react中的shouldComponentUpdate方法中,判断组件是否需要更新的时候;对于复杂的对象型数据,去对比两个对象的值是否相等是很麻烦的;而Immutable提供了 is方法,去判断两个Immutable对象值是否相等;

第三点,由于Immutable提供了丰富的API,对于操作复杂的数据结构也变得很直观和方便;

粗略阅读源码,探究Immutable数据结构如何设计 Map对象为例子:

let obj1 = Immutable.Map({
  aa: {
    'a':1
  },
  bb: {
    'b':2
  }
});
console.log(obj1)

上面的a Immutable对象打印出来是这样的:

可以看到,多层次的对象转为Immutable的Map对象时,只有第一层会被转为Immutable对象。

想要每个每层都转为Immutable对象,用fromJS:

let obj2 = Immutable.fromJS({
  aa: {
    'a':1
  },
  bb: {
    'b':2
  }
});
console.log(obj2);

截图如下:

Immutable在处理Map对象时,会将需要转成Map对象的普通对象的值放 在_root的entries对象下;这个普通对象的每个子项会是一个数组,每个子项的key是数组的第0项,value是数组的第1项; 这样存储的好处是当使用 set或者get 方法设置或取对象时,就去迭代entries对象,设置或找到相应的值。

Immutable对象使用频率最高的莫过于setIngetIn方法; setIngetIn方法第一个参数都是需要查询的层级路径,路径用数组的格式;

setIn有第二个参数,第二个参数是需要设置的值;

setIn为例,如:

obj2.setIn(['aa', 'a'], 2),  

Immutable会去从最顶层entries对象开始迭代; 查找最外层的entries对象,暂且管它叫entries0,先看entries0的第0项是不是“aa”; 如果entries0的第0项不是“aa”, 就会给entrie0中新增加一项;

比如:

obj2.setIn(['cc', 'c'], 3);

此时entries0第0项不是‘cc’, 所以会给entries0新增 ‘cc’ 对象;

如果是"aa", 就会再查询entries0的第1项;entries0的第1项在这里也是Map对象,内部也有一个entries对象,暂且叫entries1; 这里查找entrie1第0项是不是‘a’,发现是‘a’, 就给 这个‘a’赋值为2;如果不是“a”, 同理,会给entries1新增一项;

下面来看getIn操作

如果getIn的第一参数提供的层级路径在Immutable对象中找不到,会返回什么呢?

如:

let obj3 = obj2.getIn(['dd', 'd']);
console.log(obj3)   // undefined

结果返回 undefined, 这个很好理解,因为在第一个参数对应的路径下找不到想要的key,所以返回undefined;

如果此时还有第二个参数呢,结果会怎样?

let obj3 = obj2.getIn(['dd', 'd'], 5);
console.log(obj3)   // 5

结果返回5;结果证明,当使用getIn时,第一个参数无效时,结果直接返回第二个参数;(getIn方法传第二个参数没有意义);

在最开始使用Immutable时,需要学习新的API,查看一些文档,需要花费一些时间; 还需要适应新的对象操作方式,从传统的obj.a, obj[a], 转换到obj.get('a'), obj.get(['a', 'b', 'c']) 等等,但是真的投入的去使用后,Immutable会给你意想不到惊喜。