这个immutable Data 是什么鬼,有什么优点,好处等等,我就不赘述了,这篇Immutable 详解及 React 中实践讲的很透彻。
这个可变和不可变是相对于 JavaScript原生引用类型来说的。
// 原生对象 let a1 = { b: 1, c: { c1: 123 } }; let b1 = a1; b1.b = 2; console.log(a1.b, b1.b); // 2, 2 console.log(a1 === b1); // true console.log(a1.c === b1.c); // true // immutable.js 的Map let a2 = Immutable.fromJS({ b: 1, c: { c1: 123 } }); let b2 = a2.set('b', 2); // 对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象 console.log(a2.get('b'), b2.get('b')); // 1, 2 对象 a2 的 b 值并没有变成2。 console.log(a2 === b2); // false //如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。 console.log(a2.get('c') === b2.get('c')); //trueList:有序索引集,类似于 JavaScript 中的 Array。Map:类似于 JavaScript 中的 Object。OrderedMap:有序 Map,排序依据是数据的 set() 操作。Set:和 ES6 中的 Set 类似,都是没有重复值的集合。OrderedSet:Set 的变体,可以保证遍历的顺序性。排序依据是数据的 add 操作。Stack:有序集合,且使用 unshift(v) 和 shift() 进行添加和删除操作的复杂度为 O(1)Range():返回一个 Seq.Indexed 类型的数据集合,该方法接收三个参数 (start = 1, end = infinity, step = 1),分别表示起始点、终止点和步长,如果 start 等于 end,则返回空的数据结合。Repeat():返回一个 Seq.indexed 类型的数据结合,该方法接收两个参数 (value,times),value 表示重复生成的值,times 表示重复生成的次数,如果没有指定 times,则表示生成的 Seq 包含无限个 value。Record:在表现上类似于 ES6 中的 Class,但在某些细节上还有所不同。Seq:序列(may not be backed by a concrete data structure)Iterable:可以被迭代的 (Key, Value) 键值对集合,是 Immutable.js 中其他所有集合的基类,为其他所有集合提供了 基础的 Iterable 操作函数(比如 map() 和 filter)。Collection:创建 Immutable 数据结构的最基础的抽象类,不能直接构造该类型。Iterable:可以被迭代的 (Key, Value) 键值对集合,是 Immutable.js 中其他所有集合的基类,为其他所有集合提供了 基础的 Iterable 操作函数(比如 map() 和 filter)。
好吧,上面那么多乐行常用的也就是 List和Map,顶多再加个Seq。
先来说说几个重要的API吧,也是最常用的。
fromJS() 是最最最常用的将原生JS数据转换为ImmutableJS数据的转换方法。使用方式类似于 JSON.parse(),接收两个参数:json 数据和 reviver 函数。在不传递reviver函数的情况下,默认将原生JS的Array转为List,Object转为Map.
// 常见 const t1 = Immutable.fromJS({a: {b: [10, 20, 30]}, c: 40}); console.log(t1); // 不常用 const t2 = Immutable.fromJS({a: {b: [10, 20, 30]}, c: 40}, function(key, value) { // 定制转换方式,下这种就是将Array转换为List,Object转换为Map const isIndexed = Immutable.Iterable.isIndexed(value); return isIndexed ? value.toList() : value.toOrderedMap(); // true, "b", {b: [10, 20, 30]} // false, "a", {a: {b: [10, 20, 30]}, c: 40} // false, "", {"": {a: {b: [10, 20, 30]}, c: 40}} }); console.log(t2);fromJS()的源码:
function fromJS(json, converter) { return converter ? fromJSWith(converter, json, '', {'': json}) : fromJSDefault(json); } function fromJSDefault(json) { if (Array.isArray(json)) { return IndexedSeq(json).map(fromJSDefault).toList(); } if (isPlainObj(json)) { return KeyedSeq(json).map(fromJSDefault).toMap(); } return json; }通过源码可以发现其默认只转换Object和Array,其他原生类型原封不动返回。
先来看官网的一段话:
immutable数据应该被当作值而不是对象,值是表示该事件在特定时刻的状态。这个原则对理解不可变数据的适当使用是最重要的。为了将Immutable.js数据视为值,就必须使用Immutable.is()函数或.equals()方法来确定值相等,而不是确定对象引用标识的 === 操作符。
所以toJS()就是用来对两个immutable对象进行值比较的。使用方式类似于 Object.is(obj1, obj2),接收两个参数。
const map1 = Immutable.Map({a:1, b:1, c:1}); const map2 = Immutable.Map({a:1, b:1, c:1}); // 两个不同的对象 console.log(map1 === map2); // false // 进行值比较 console.log(Immutable.is(map1, map2)); // true // 不仅仅只能比较ImmutableJS的类型的数据 console.log(Immutable.is(undefined, undefined)); // true console.log(Immutable.is(null, undefined)); // false console.log(Immutable.is(null, null)); // true console.log(Immutable.is(NaN, NaN)); // true // 区别于 Object.is console.log(Object.is(0, -0) ,Immutable.is(-0, 0)); // false , true源码:
function is(valueA, valueB) { if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { return true; } if (!valueA || !valueB) { return false; } if (typeof valueA.valueOf === 'function' && typeof valueB.valueOf === 'function') { valueA = valueA.valueOf(); valueB = valueB.valueOf(); if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { return true; } if (!valueA || !valueB) { return false; } } if (typeof valueA.equals === 'function' && typeof valueB.equals === 'function' && valueA.equals(valueB)) { return true; } return false; }上面这段源码写的很精练优雅,值得学习和借鉴。
Map 数据类型,对应原生 Object 数组。最最常用的 数据结构之一,循环时无序(orderedMap有序),对象的 key 可以是任意值。具体看下面的例子:
console.log(Map().set(List.of(1), 'list-of-one').get(List.of(1))); console.log(Map().set(NaN, 'NaN').get(NaN)); console.log(Map().set(undefined, 'undefined').get(undefined)); console.log(Map().set(null, 'null').get(null));OrderedMap 是 Map 的变体,它除了具有 Map 的特性外,还具有顺序性,当开发者遍历 OrderedMap 的实例时,遍历顺序为该实例中元素的声明、添加顺序。OrderedMap比非有序Map更昂贵,并且可能消耗更多的内存。如果真要求遍历有序,请使用List。
List 数据类型,对应原生 Array 数组。和原生数组,最大区别不存在'空位'。[, , , , ]
console.log(List([,,,,]).toJS());// [undefined, undefined, undefined, undefined]我们主要介绍Map 和 List。
构造函数不常用,一般都是通过Immutable.fromJS()将一个JS原生对象转换为一个Immutable对象。
同Key覆盖问题
//最后的{key: value2} 覆盖了前面的 {key: value} console.log(Map([["key", "value"], ["key", "value2"], ["key1", "value1"]]).toJS());// {key: "value2", key1: "value1"}判断是否是一个Map , 对原生Object不生效
console.log(Map.isMap({})); // false console.log(Map.isMap(Map({}))); // true判断是否是一个List , 对原生Array不生效
console.log(List.isList([])); // false console.log(List.isList(List([]))); // truecountBy()和count()的区别就是它的返回值是一个对象。
// Map console.log(Immutable.fromJS({key: 1, key1: 34}).countBy((value, key, obj) => { return value > 3; }).toJS());// {false: 1, true: 1} // list console.log(Immutable.fromJS([1, 2, 5, 6]).countBy((value, index, array) => { return value > 3; }).toJS());// {false: 2, true: 2}ps: count() 兼容惰性计算 countBy() 不兼容,先不做深研究。
默认值undefined
console.log(List([]).setSize(2).toJS()); // [undefined, undefined]List数据类型也拥有pop、push、shift、unshift这四种操作方法,和原生Array的四种方法使用方式一致,但唯一区别就是返回新的List,并且不改变原来的数组本身,而原生则是会改变元素本身。
// ImmutableJS:返回新的List,并且不改变元素本身 const $test = List([1, 2, 3, 4]); console.log($test.pop().toJS(), $test.toJS()); // [1, 2, 3] [1, 2, 3, 4] // 原生:返回被改变的值,改变元素本身 const test = [1, 2, 3, 4]; console.log(test.pop(), test); // 4 [1, 2, 3]和 setIn 使用方式一致
修改某一个元素
上面已经介绍过
update(key: K, notSetValue: V, updater: (value: V) => V): Map<K, V>
// List const $arr1 = Immutable.fromJS([1, 2, 3]); console.log($arr1.update('2', (value)=> { return value * 2; }).toJS(), $arr1.toJS());// [1, 2, 6] [1, 2, 3] console.log($arr1.update('6', 1, (value)=> { return value * 2; }).toJS(), $arr1.toJS());// [1, 2, 3, undefined, undefined, undefined, 2] [1, 2, 3] console.log($arr1.update('6', 0, (value)=> { // 默认值必须大于0 感觉有BUG,所以还是不要用了。 return value * 2; }).toJS(), $arr1.toJS());// [1, 2, 3] [1, 2, 3] // Map const $obj1 = Immutable.fromJS({a: {a1: 34}, b: 2, c: 3, d: 444}); console.log($obj1.update('a', (value)=> { return value * 2; }).toJS(), $obj1.toJS());// {a: 2, b: 2, c: 3, d: 444} {a: 1, b: 2, c: 3, d: 444} console.log($obj1.update('e', 1, (value)=> { return value * 2; }).toJS(), $obj1.toJS());// {a: 1, b: 2, c: 3, d: 444, e: 2} {a: 1, b: 2, c: 3, d: 444} console.log($obj1.update('e', 0, (value)=> { // 默认值入手是number必须大于0 感觉有BUG,所以还是不要用了。 return value * 2; }).toJS(), $obj1.toJS());// {a: 1, b: 2, c: 6, d: 444} {a: 1, b: 2, c: 3, d: 444}使用方式和setIn一样。
使用方式:get(key: number, notSetValue?: T)
// List const $test = Immutable.fromJS([1111111, 22222, {a: 888123}]); console.log($test.get(0)); // 1111111 // 只有数组可以用 number 类型 的key console.log(Immutable.fromJS({1: 'abc'}).get(1), Immutable.fromJS({1: 'abc'}).get('1'));// undefined "abc" | 只有数组可以用 number 类型 的key // notSetValue 默认值,了解 console.log($test.get(11, 'no have value')); // no have value // getIn console.log($test.getIn(['2', 'a'], 'child no have value')); // 888123 console.log($test.getIn(['2', 'b'], 'child no have value')); // child no have value // Map const $test = Immutable.fromJS({a: {a1: 222}, b: 2, c: 3, d: 444}); console.log($test.get('a')); // 1111111 // notSetValue 默认值,了解 console.log($test.get('v', 'no have value')); // no have value // getIn console.log($test.getIn(['a', 'a1'], 'child no have value')); // 222 console.log($test.getIn(['d', 'b1'], 'child no have value')); // child no have valuefind()、findLast() 返回 value。
// List console.log(Immutable.fromJS([1, 2, 56, {a: {b: 111}}]).find((value, index, array) => { return index === 3; }).toJS());// {a: {b: 111}} // Map console.log(Immutable.fromJS({a: {a1: 222}, b: 2, c: 3, d: 444}).find((value, key, obj) => { return value === 3; }));// 3findKey()、findLastKey() 返回 key
// List console.log(Immutable.fromJS([1, 2, 3, {a: {b: 111}}]).findKey((value, index, array) => { return index === 3; }));// 3 // Map console.log(Immutable.fromJS({a: {a1: 222}, b: 2, c: 3, d: 444}).findKey((value, key, obj) => { return value === 3; }));// cfindEntry()、findLastEntry() 返回 key:value。
// List console.log(Immutable.fromJS([1, 2, 3, {a: {b: 111}}]).findEntry((value, index, array) => { return index === 3; }));// [3, Map] // Map console.log(Immutable.fromJS({a: {a1: 222}, b: 2, c: 3, d: 444}).findEntry((value, key, obj) => { return Immutable.is(value, Immutable.fromJS({a1: 222})); }));// ["a", Map]keyOf()、lastKeyOf() 根据 value 返回 key。
// List console.log(Immutable.fromJS([1, 2, 3, {a: {b: 111}}]).keyOf(Immutable.fromJS({a: {b: 111}}))); // 3 console.log(Immutable.fromJS([1, 2, 3, {a: {b: 111}}]).keyOf(2)); // 1 // Map console.log(Immutable.fromJS({a: {a1: 222}, b: 2, c: 3, d: 444}).keyOf(Immutable.fromJS({a1: 222}))); // a console.log(Immutable.fromJS({a: {a1: 222}, b: 2, c: 3, d: 444}).keyOf(2)); // bmax()、maxBy()默认比较规则为>,min()、minBy()默认比较规则为>。
同max()
同maxBy()
获取ES6 Iterable 迭代器。
// List const $test = List([11, 22, 33, 44]); const keys = $test.keys(); for (let i of keys) { console.log(i); } const values = $test.values(); for (let i of values) { console.log(i); } const entries = $test.entries(); for (let i of entries) { console.log(i); } // Map const $test = Immutable.fromJS({a: {a1: 222}, b: 2, c: 3, d: 444}); const keys = $test.keys(); for (let i of keys) { console.log(i); // a b c d } const values = $test.values(); for (let i of values) { console.log(i); // {a1: 222} 2 3 444 } const entries = $test.entries(); for (let i of entries) { console.log(i);// ["a", Map] ["b", 2] ["c", 3] ["d", 444] }和原生Array slice()用法一致。
// List console.log(Immutable.fromJS([1, 2, 3]).slice(0).toJS());// [1, 2, 3] // Map console.log(Immutable.fromJS({a: {a1: 34}, b: 2, c: 3, d: 444}).slice(0).toJS());// {a: Object, b: 2, c: 3, d: 444} console.log(Immutable.fromJS({a: {a1: 34}, b: 2, c: 3, d: 444}).slice(1).toJS());// {b: 2, c: 3, d: 444}map() filter() every() some() forEach() reduce() reduceRight()。
// List //1. map() console.log(Immutable.fromJS([1, 2, 3, 4, 5]).map((value, index, array)=>{ return value * 2; }).toJS()); // [2, 4, 6, 8, 10] //2. filter() console.log(Immutable.fromJS([1, 2, 3, 4, 5]).filter((value, index, array)=>{ return value % 2 === 0; }).toJS()); // [2, 4] // filterNot() ...这个没有什么卵用 //3. every() console.log(Immutable.fromJS([1, 2, 3, 4, 5]).every((value, index, array)=>{ return value > 2; })); // false //4. some() console.log(Immutable.fromJS([1, 2, 3, 4, 5]).some((value, index, array)=>{ return value > 2; })); // true //5. forEach() 返回迭代的条目数(包括返回false的最后一个迭代) // 与Array 的 forEach不同,如果sideEffect的任何调用返回false,迭代将停止。 返回迭代的条目数(包括返回false的最后一个迭代)。 console.log(Immutable.fromJS([1, 2, 3, 4, 5, {a: 123}]).forEach((value, index, array)=>{ console.log(value, index, array.toJS(), 'forEach'); return value < 5; })); // 5 //6. reduce() // 同原生用法 //7. reduceRight() // 同原生用法 // Map //1. map() console.log(Immutable.fromJS({a: 5, b: 2, c: 3, d: 444}).map((value, key, obj)=>{ return value * 2; }).toJS()); // {a: 10, b: 4, c: 6, d: 888} //2. filter() console.log(Immutable.fromJS({a: 5, b: 2, c: 3, d: 444}).filter((value, key, obj)=>{ return value % 2 === 0; }).toJS()); // {b: 2, d: 444} // filterNot() ...这个没有什么卵用 //3. every() console.log(Immutable.fromJS({a: 5, b: 2, c: 3, d: 444}).every((value, key, obj)=>{ return value > 2; })); // false //4. some() console.log(Immutable.fromJS({a: 5, b: 2, c: 3, d: 444}).some((value, key, obj)=>{ return value > 2; })); // true //5. forEach() 返回迭代的条目数(包括返回false的最后一个迭代) // 与Array 的 forEach不同,如果sideEffect的任何调用返回false,迭代将停止。 返回迭代的条目数(包括返回false的最后一个迭代)。 console.log(Immutable.fromJS({a: 5, b: 2, c: 3, d: 444}).forEach((value, key, obj)=>{ return value < 444; })); // 4 //6. reduce() // 同原List用法 //7. reduceRight() // 同List用法对Map元素进行处理,返回处理后的对象。
//mapKeys() 返回对象 console.log(Immutable.fromJS({a: 5, b: 2, c: 3, d: 444}).mapKeys((key)=>{ return key + 'hhh'; }).toJS()); // {ahhh: 5, bhhh: 2, chhh: 3, dhhh: 444} //mapEntries() 返回对象 console.log(Immutable.fromJS({a: 5, b: 2, c: 3, d: 444}).mapEntries(([key, value])=>{ return [key + 'aaa', value+'hhhh']; }).toJS());// {aaaa: "5hhhh", baaa: "2hhhh", caaa: "3hhhh", daaa: "444hhhh"}merge() mergeDeep() mergeWith() mergeDeepWith()
// List const $test = Immutable.fromJS([1, 2, 3, 7, {a: {b: 55, c: 66}}]); const $test1 = Immutable.fromJS([1, 2, 3, 6, {a: {b: 333, d: 67}}]); // 浅merge console.log($test.merge($test1).toJS(), $test.toJS()); // $test1 -> $test [1, 2, 3, 6, {b: 333, d: 67}] [1, 2, 3, 7, {a: {b: 55, c: 66}}] // 深merge console.log($test.mergeDeep($test1).toJS(), $test.toJS()); // $test1 -> $test [1, 2, 3, 6, {b: 333, c: 66, d: 67}] [1, 2, 3, 7, {a: {b: 55, c: 66}}] // 浅merge自定义merge规则 console.log($test.mergeWith((prev, next)=> { // 自定义转换 return prev; }, $test1).toJS(), $test1.toJS()); // 深merge自定义merge规则 console.log($test.mergeDeepWith((prev, next)=> { // 自定义转换 return prev; }, $test1).toJS(), $test1.toJS()); // Map const $test = Immutable.fromJS({a: {a1: 222, a3: 456}, b: 2, c: 3, d: 444}); const $test1 = Immutable.fromJS({a: {a1: 222, a2: 234}, b: 2, c: 3, d: 444}); // 浅merge console.log($test.merge($test1).toJS(), $test.toJS()); // $test1 -> $test {a: {a1: 222, a2: 234}, b: 2, c: 3, d: 444} {a: {a1: 222, a3: 456}, b: 2, c: 3, d: 444} // 深merge console.log($test.mergeDeep($test1).toJS(), $test.toJS()); // $test1 -> $test {a: {a1: 222, a2: 234, a3: 456}, b: 2, c: 3, d: 444} {a: {a1: 222, a3: 456}, b: 2, c: 3, d: 444} // 浅merge自定义merge规则 console.log($test.mergeWith((prev, next)=> { // 自定义转换 return prev; }, $test1).toJS(), $test1.toJS()); // 深merge自定义merge规则 console.log($test.mergeDeepWith((prev, next)=> { // 自定义转换 return prev; }, $test1).toJS(), $test1.toJS());使用方式和原生Array的joni()一样。
// List console.log(Immutable.fromJS([1, 2, 3, {a: 123, b: 321}]).join()); // 1,2,3,Map { "a": 123, "b": 321 } // Map console.log(Immutable.fromJS({b: 2, a: {a1: 222, a3: 456}, c: 3, d: 444}).join()); // 2,Map { "a1": 222, "a3": 456 },3,444includes()、contains() 这俩等效。
// List // 对象是否包含某个元素,对Immutable元素使用Immutable.is 进行比较 console.log(Immutable.fromJS([6, 5, 4, 3, 2, 1, 89]).includes('89'));// 数组没有字符89,所以返回 false console.log(Immutable.fromJS([6, 5, 4, 3, 2, 1, '89']).contains('89'));// true console.log(Immutable.fromJS([6, 5, 4, 3, 2, 1, Immutable.fromJS([6, 5, 4])]).contains(Immutable.fromJS([6, 5, 4])));// true // Map // 对象是否包含某个元素,对Immutable元素使用Immutable.is 进行比较 console.log(Immutable.fromJS({b: 2, a: {a1: 222, a3: 456}, c: 3, d: 89}).includes('89'));// 数组没有字符89,所以返回 false console.log(Immutable.fromJS({b: 2, a: {a1: 222, a3: 456}, c: 3, d: '89'}).contains('89'));// true console.log(Immutable.fromJS({b: 2, a: {a1: 222, a3: 456}, c: 3, d: Immutable.fromJS([6, 5, 4])}).contains(Immutable.fromJS([6, 5, 4])));// truesort()和sortBy()。
// List // sort(comparator?: (valueA: V, valueB: V) => number): Iterable<K, V> console.log(Immutable.fromJS([6, 5, 4, 3, 2, 1]).sort().toJS()); // 传入比较函数 console.log(Immutable.fromJS([1, 2, 3, 4, 5, 6]).sort((a, b) => { if (a < b) { return -1; } if (a > b) { return 1; } if (a === b) { return 0; } }).toJS()); // sortBy /* sortBy<C>( comparatorValueMapper: (value: T, key: number, iter: Iterable<number, T>) => C, comparator?: (valueA: C, valueB: C) => number ): Iterable<number, T> */ console.log(Immutable.fromJS([{a: 1, b: {c: 22}}, {a: 2, b: {c: 22}}, {a: 1, b: {c: 22}}, {a: 3, b: {c: 22}}, {a: 10, b: {c: 22}}, {a: 9, b: {c: 22}}]).sortBy((value, index, array)=> { return value.get('a') },(a, b) => { if (a < b) { return -1; } if (a > b) { return 1; } if (a === b) { return 0; } }).toJS()); // Map console.log(Immutable.fromJS({b: 2, a: 88, c: 3, d: 5}).sort().toJS());// {b: 2, c: 3, d: 5, a: 88} // 传入比较函数 console.log(Immutable.fromJS({b: 2, a: 88, c: 3, d: 5}).sort((a, b) => { if (a < b) { return -1; } if (a > b) { return 1; } if (a === b) { return 0; } }).toJS());// {b: 2, c: 3, d: 5, a: 88} // sortBy /* sortBy<C>( comparatorValueMapper: (value: T, key: number, iter: Iterable<number, T>) => C, comparator?: (valueA: C, valueB: C) => number ): Iterable<number, T> */ console.log(Immutable.fromJS({b: {a: 2}, a: {a: 88}, c: {a: 3}, d: {a: 5}}).sortBy((value, key, obj)=> { return value.get('a') },(a, b) => { if (a < b) { return -1; } if (a > b) { return 1; } if (a === b) { return 0; } }).toJS());// {b: {a: 2}, c: {a: 3}, d: {a: 5}, a: {a: 88}}参数默认情况下,false 深度平铺,true 浅度平铺1层。
// List console.log(Immutable.fromJS([1, 2, 3, 4, [1, 11, 111, 12344], {a: 1234, b: {bb: [777, 888]}}, 5, 6]).flatten().toJS()); // [1, 2, 3, 4, 1, 11, 111, 12344, 1234, 777, 888, 5, 6] console.log(Immutable.fromJS([1, 2, 3, 4, [1, 11, 111, 12344], {a: 1234, b: {bb: [777, 888]}}, 5, 6]).flatten(true).toJS()); // [1, 2, 3, 4, 1, 11, 111, 12344, 1234, Object, 5, 6] // Map console.log(Immutable.fromJS({b: 2, a: {a1: {a5: 333}, a3: [1,2,3]}, c: 3, d: 5}).flatten().toJS()); // {0: 1, 1: 2, 2: 3, b: 2, a5: 333, c: 3, d: 5} console.log(Immutable.fromJS({b: 2, a: {a1: {a5: 333}, a3: [1,2,3]}, c: 3, d: 5}).flatten(true).toJS()); // {b: 2, a1: Object, a3: Array[3], c: 3, d: 5}另外还有一个flatMap()方法,它就等同于List([1,2,3,4,5,6]).map(...).flatten(true)。
返回值是OrderedMap。
// List console.log(Immutable.fromJS([{v: 0, a: 111}, {v: 1, a: {b: [1, 2, 3]}}, {v: 1, a: 333}, {v: 0, a: {b: [1, 2, 3]}}, {v: 1, a: 333}]).groupBy((value) => { return value.get('a') }).toJS()); // OrderedMap {111: Array[1], 333: Array[2], Map { "b": List [ 1, 2, 3 ] }: Array[2]} // Map console.log(Immutable.fromJS({b: {a5: 333}, a: {a5: 333}, c: {a5: 334}, d: {a5: 334}}).groupBy((value) => { return value.get('a5') }).toJS()); // OrderedMap {333: {b: {a5: 333}, a: {a5: 333}}, 334: {c: {a5: 334}, d: {a5: 334}}}暂时不做详细介绍。
// toMap() // toOrderedMap() // toSet() // toOrderedSet() // toList() // toStack()在上面的代码中,进行三次push()操作,会分别产生三个List,其中头两次中间List明显是冗余的,我们其实只关心最后一次产生的List,这种情况就产生了冗余操作,为了消除这种冗余的操作,减少其中的性能损耗,我们可以先暂时把$list1变成非immutable的,这样我们进行push操作时就不会产生冗余的中间List。
这里面有性能比较:stack overflow 中的一个问题。在把Immutable类型的数据,转为可变的之后,只有部分操作方法会返回可变的数据,其他操作方法仍然会返回immutable数据,但是这些方法应该就够用了。
// Map: set(), merge() // List: push(), pop(), shift(), unshift()为此 withMutations() 函数把list临时变为可变的数据。
// List // 将传入的对象变成可变的对象,为了提高性能 const $list1 = Immutable.List.of(1,2,3); const $list2 = $list1.withMutations(function ($list) { $list.push(4).push(5).push(6); // 为此 withMutations 函数把list临时变为可变的数据,这三次push实质上只产生了一个中间态list }); console.log($list1.size);// 3 console.log($list2.size);// 6 // Map const $obj1 = Immutable.fromJS({b: 2, a: [1, 2, 2]}); const $obj2 = $obj1.withMutations(function ($obj) { $obj.set('c', 4444).set('d', 4444).set('e', 4444); }); console.log($obj1.size);// 2 console.log($obj2.size);// 5这两个一定要配对使用.
// List demo: const $test1 = Immutable.List.of(1,2,3); const $test2 = $test1.asMutable(); // 变成可变对象 console.log($test1 === $test2, Immutable.is($test1, $test2)); // false true const $test3 = $test2.set(0, 123); // 这里没有产生新的对象 console.log($test3.toJS(), $test2.toJS(), $test3 === $test2, Immutable.is($test3, $test2));// [123, 2, 3] [123, 2, 3] true true // 变成不可变对象 const $test4 = $test3.asImmutable(); const $test5 = $test4.set(0, 234); console.log($test4 === $test3, Immutable.is($test4, $test3));// true true console.log($test4.toJS(), $test5.toJS(), $test4 === $test5, Immutable.is($test4, $test5));// [123, 2, 3] [234, 2, 3] false false // Map demo: const $test1 = Immutable.fromJS({b: 2, a: [1, 2, 2]}); const $test2 = $test1.asMutable(); // 变成可变对象 console.log($test1 === $test2, Immutable.is($test1, $test2)); // false true const $test3 = $test2.set('b', 123); // 这里没有产生新的对象 console.log($test3.toJS(), $test2.toJS(), $test3 === $test2, Immutable.is($test3, $test2));// {b: 123, a: Array[3]} {b: 123, a: Array[3]} true true // const $test3 = $test2.slice(1); // 这里没有产生新的对象 // console.log($test3.toJS(), $test2.toJS(), $test3 === $test2, Immutable.is($test3, $test2));// {a: Array[3]} {b: 2, a: Array[3]} false false // 变成不可变对象 const $test4 = $test3.asImmutable(); const $test5 = $test4.set('b', 234); console.log($test4 === $test3, Immutable.is($test4, $test3));// true true console.log($test4.toJS(), $test5.toJS(), $test4 === $test5, Immutable.is($test4, $test5));// {b: 234, a: Array[3]} {b: 123, a: Array[3]} [234, 2, 3] false false利用 immutable.js 不可变的特性,可以极大的优化React render的冗余执行。React 官方提供的PureRenderMixin是浅比较,具体的不用细说,网上一查一堆。
专门针对immutable的PureRenderMixin,用来装饰React组件。
import {React} from 'base'; import pureRenderDecorator from '../../../widgets/libs/immutable-pure-render-decorator'; @pureRenderDecorator export default class PartA extends React.Component { constructor(props) { super(props); // 舍弃React.addons.PureRenderMixin // this.shouldComponentUpdate = React.addons.PureRenderMixin.shouldComponentUpdate.bind(this); } render() { console.log('组件PartA,render执行了'); const data = this.props.data; return ( <section> <div> <p>我是组件PartA</p> <p>{data.toJSON ? JSON.stringify(data.toJSON()) : data}</p> </div> </section> ) } }我们都知道官方提供的React.addons.PureRenderMixin提供的shouldComponentUpdate(),只能进行浅比较,对于引用类型Object、Array比较无力,而如果使用Immutable的Map和List替换Object、Array,则可以使用Immutable.is()来比较两个引用类型,从而补充了React.addons.PureRenderMixin的漏洞。
源码见附件
对于使用immutable.js的项目,在应用公共组件的时候,由于公共组件的内部实现一定是原生JS数据,所以我们只能传递原生JS数据到公共组件,但是如果转换成了原生JS数据,就又会出现"React.addons.PureRenderMixin提供的shouldComponentUpdate()是浅比较"问题,对此可以使用下面的高阶组件进行封装。
/** * Created by 百竿<shengdong.ysd@alibaba-inc.com> on 2017-2-6. */ import {React} from 'base'; // 通过Immutable.is 封装过的 shouldComponentUpdate import {shouldComponentUpdate} from '../immutable-pure-render-decorator'; export default ComposedComponent => { return class extends React.Component { constructor(props) { super(props); this.shouldComponentUpdate = shouldComponentUpdate.bind(this); } render() { const props = this.props.toJS ? this.props.toJS() : this.props; return <ComposedComponent {...this.props} {...props} />; } } };demo:
import {React} from 'base'; import { connect } from 'react-redux'; import highComponent from '../../../../widgets/libs/utils/highComponent'; import actions from '../../actions'; // 公共组件 import Dialog from '@alife/dialog'; // import Immutable from 'immutable'; function mapStateToProps(state) { return { open: state.getIn(['dialog', 'open']), title: state.getIn(['dialog', 'title']) } } function mapDispatchToProps(dispatch) { return { onPrimaryTouchTap: ()=> { dispatch(actions.toggleDialog(false)); }, onSecondaryTouchTap: ()=> { dispatch(actions.toggleDialog(false)); } } } export default connect(mapStateToProps, mapDispatchToProps)(highComponent(Dialog))//通过高阶组件封装 相关资源:python入门教程(PDF版)