不可变数据合集 - immutable

311 阅读4分钟

简介

Immutable collections for JavaScript

JavaScript 的不可变集合

javaScript中,引用对象一般是可变的,通常我们使用用deepCopy解决原数据跟随新数据改变的问题。

而使用Immutable,数据一旦创建,就是一个不可变集合,所有会改变集合的方法都会返回一个新的不可变集合,也就不需要deepCopy。

  function touchAndLog(touchFn) {
    let data = { key: 'value' };
    touchFn(data);
    console.log(data.key); // 猜猜会打印什么?
  }

上面代码中,data是Mutable的话,由于不知道touchFn对data做了什么,所以无法得知最终会打印什么,但是如果data是Immutable的,就可以很明确的知道打印的是value

Immutable 带来的优点是降低了Mutable带来的复杂度,Mutable的数据耦合了Time和Value的概念,数据很难回溯。而Immutable的不可变性,解决了这个问题

Immutable 最大的缺点是侵入性极强

安装

yarn add immutable

代码示例

小技巧:为了不与原生API混淆,这里使用as设置别名

Map

  import { Map as ImmutableMap } from 'immutable'
  const map1 =ImmutableMap({ a1b2c3 }); 
  const map2 = map1.set('b'50); 
  console.log(map2.get('b')); // 2
  console.log(map1.get('b')); // 50

List

  import { List as ImmutableList } from 'immutable'
  const list1 =ImmutableList ([ 12 ]);
  const list2 = list1.push(345);  // [1,2,3,4,5]
  const list3 = list2.unshift(0);    // [0,1,2,3,4,5]
  const list4 = list1.concat(list2, list3); // [1,2,3,4,5,0,1,2,3,4,5]

push, set, unshift or splice 都可以直接用,返回一个新的immutable对象

merge和concat

merge() 连接对象 ,concat() 连接数组

  import { Map as ImmutableMapList as ImmutableList } from 'immutable'
  const map1 =ImmutableMap({ a1b2c3d4 });
  const map2 =ImmutableMap({ c10a20t30 });
  const obj = { d100o200g300 };
  const map3 = map1.merge(map2, obj); // Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
  const list1 = ImmutableList([ 123 ]);
  const list2 = ImmutableList([ 456 ]);
  const list3 = list1.concat(list2, array); // List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

toJS

toJS() 把immutable对象转换为js对象

  import { Map as ImmutableMapList as ImmutableList } from 'immutable'
  const deep =ImmutableMap({ a1b2c:ImmutableList ([ 345 ]) });
  console.log(deep.toObject());   // { a: 1, b: 2, c: List [ 3, 4, 5 ] }
  console.log(deep.toArray());    // [ 1, 2, List [ 3, 4, 5 ] ]
  console.log(deep.toJS());       // { a: 1, b: 2, c: [ 3, 4, 5 ] }
  JSON.stringify(deep);           // '{"a":1,"b":2,"c":[3,4,5]}'

fromJS

fromJS() 包裹 js对象转换为immutable对象

const { fromJS } = require('immutable');
const nested = fromJS({ a: { b: { c: [ 345 ] } } });// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
const nested2 = nested.mergeDeep({ a: { b: { d6 } } });// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
console.log(nested2.getIn([ 'a''b''d' ])); // 6
//如果取一级属性 直接通过get方法,如果取多级属性 getIn(["a","b","c"]])

// setIn 设置新的值
const nested3 = nested2.setIn([ 'a''b''d' ], "kerwin");
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: "kerwin" } } }

// updateIn 回调函数更新
const nested3 = nested2.updateIn([ 'a''b''d' ], value => value + 1);
console.log(nested3);
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
const nested4 = nested3.updateIn([ 'a''b''c' ], list => list.push(6));
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }

判断相等性

immutable应当被视为,而不是对象.

所以对于引用对象需要用Immutable.is() 或者.equals()来判断相等性,而不是用===

  import { Map as ImmutableMap } from 'immutable'
  const map1 = ImmutableMap({ a: 1, b: 2, c: 3 });
  const map2 = ImmutableMap({ a: 1, b: 2, c: 3 });
  console.log(map1.equals(map2)); // true
  console.log(map1 === map2); // false

注意:Immutable.js 避免为值没有发生变化的更新创建新对象,以允许有效的引用相等性检查来快速确定是否没有发生变化。 如下操作,是可以用===运算符的

  import { Map as ImmutableMap } from 'immutable'
  const map1 = ImmutableMap({ a: 1, b: 2, c: 3 });
  const map2 = map1.set('b', 2); // Set to same value
  console.log(map1.equals(map2)); // true
  console.log(map1 === map2); // true

注意2:两次操作是互相独立的,如下map2map3===会返回false

  import { Map as ImmutableMap, is } from 'immutable'
  const map1 = ImmutableMap({ a: 1, b: 2, c: 3 });
  const map2 = map1.set('b', 3); // Set to same value
  const map3 = map1.set('b', 3); // Set to same value
  console.log(map3 === map2);  // false
  console.log(is(map2, map3)); // true
  console.log(map2.equals(map3)) // true

深度嵌套

常用的有mergeDeepgetInsetIn,和updateIn

  const { fromJS } = require('immutable');
  const nested = fromJS({ a: { b: { c: [3, 4, 5] } } });

  const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });
  // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }

  console.log(nested2.getIn(['a', 'b', 'd'])); // 6

  const nested3 = nested2.updateIn(['a', 'b', 'd'], value => value + 1);
  console.log(nested3);
  // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }

  const nested4 = nested3.updateIn(['a', 'b', 'c'], list => list.push(6));
  // Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }

Lazy Seq

Seq描述了一个惰性操作,允许通过不创建中间集合来有效地链接所有高阶集合方法(例如map和filter)的使用。

  • Seq 是不可变的
  • Seq 是惰性的

一旦Seq被使用,它就只执行必要的工作。在这个例子中,没有创建中间数组,filter 被调用了 3 次,map 只被调用了一次:

  const { Seq } = require('immutable');
  const oddSquares = Seq([1, 2, 3, 4, 5, 6, 7, 8])
    .filter(x => x % 2 !== 0)
    .map(x => x * x);
  oddSquares.get(1) // 9

更多

immutable 官网