整理于互联网
一、前言
从问题说起:熟悉 React 组件生命周期的话都知道:调用 setState 方法总是会触发 render 方法从而进行 vdom re-render 相关逻辑,哪怕实际上你没有更改到 Component.state
1 2 this .state = {count : 0 }this .setState({count : 0 });
为了避免这种性能上的浪费,React 提供了一个 shouldComponentUpdate 来控制触发 vdom re-render 逻辑的条件。于是 PureRenderMixin 作为一种优化技巧被使用。它仅仅是浅比较对象,深层次的数据结构根本不管用
js中的Immutable Data
在javascript中我们可以通过deep clone来模拟Immutable Data,就是每次对数据进行操作,新对数据进行deep clone出一个新数据
deep clone
当然你或许意识到了,这样非常的慢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 'use strict' ; var cloneDeep = require ('lodash.clonedeep' );var data = { id: 'data' , author: { name: 'mdemo' , github: 'https://github.com/demohi' } }; var data1 = cloneDeep(data);console .log('equal:' , data1===data); data1.id = 'data1' ; data1.author.name = 'demohi' ; console .log(data.id);console .log(data1.id);console .log(data.author.name);console .log(data1.author.name);
这时候 immutableJS 就派得上用场了
1 2 3 var map1 = Immutable.fromJS({a :1 , b :1 , c :{b :{c :{d :{e :7 }}}}});var map2 = Immutable.fromJS({a :1 , b :1 , c :{b :{c :{d :{e :7 }}}}});Immutable.is(map1, map2);
1 Immutable.fromJS({a :1 , b :2 , c :3 }).map(function (value, key ) { });
二、什么是 Immutable Data
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象
Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变
同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing····(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
打印immutableJS看看有什么东西
image.png
一个说明不可变的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 let a1 = { b: 1 , c: { c1: 123 } }; let b1 = a1;b1.b = 2 ; console .log(a1.b, b1.b); console .log(a1 === b1); console .log(a1.c === b1.c); let a2 = Immutable.fromJS({ b: 1 , c: { c1: 123 } }); let b2 = a2.set('b' , 2 );console .log(a2.get('b' ), b2.get('b' )); console .log(a2 === b2); console .log(a2.get('c' ) === b2.get('c' ));
三、为什么要在React.js中使用Immutable
它是一个完全独立的库,无论基于什么框架都可以用它。意义在于它弥补了Javascript 没有不可变数据结构的问题
由于是不可变的,可以放心的对对象进行任意操作。在React开发中,频繁操作state对象或是store,配合immutableJS快、安全、方便
熟悉React.js的都应该知道,React.js是一个UI = f(states)的框架,为了解决更新的问题,React.js使用了virtual dom,virtual dom通过diff修改dom,来实现高效的dom更新。
但是有一个问题。当state更新时,如果数据没变,你也会去做virtual dom的diff,这就产生了浪费。这种情况其实很常见
当然你可能会说,你可以使用PureRenderMixin来解决呀,PureRenderMixin是个好东西,我们可以用它来解决一部分的上述问题
但PureRenderMixin只是简单的浅比较,不使用于多层比较。那怎么办?自己去做复杂比较的话,性能又会非常差
方案就是使用immutable.js可以解决这个问题。因为每一次state更新只要有数据改变,那么PureRenderMixin可以立刻判断出数据改变,可以大大提升性能
Immutable 优点
Immutable 降低了 Mutable 带来的复杂度
可变(Mutable)数据耦合了 Time和 Value 的概念,造成了数据很难被回溯
Immutable.js 使用了 Structure Sharing 会尽量复用内存,甚至以前使用的对象也可以再次被复用。没有被引用的对象会被垃圾回收
1 2 3 4 5 6 7 8 9 import { Map } from 'immutable' ;let a = Map ({ select: 'users' , filter: Map ({ name : 'Cam' }) }) let b = a.set('select' , 'people' );a === b; a.get('filter' ) === b.get('filter' );
Undo/Redo,Copy/Paste,甚至时间旅行这些功能做起来小菜一碟
因为每次数据都是不一样的,只要把这些数据放到一个数组里储存起来,想回退到哪里就拿出对应数据即可,很容易开发出撤销重做这种功能。
传统的并发非常难做,因为要处理各种数据不一致问题,因此『聪明人』发明了各种锁来解决。但使用了 Immutable 之后,数据天生是不可变的,并发锁就不需要了。
Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。
Immutable 缺点
需要学习新的 API
增加了资源文件大小
容易与原生对象混淆
四、Immutable 的几种数据类型
List: 有序索引集,类似JavaScript中的Array。
Map: 无序索引集,类似JavaScript中的Object。
OrderedMap: 有序的Map,根据数据的set()进行排序。
Set: 没有重复值的集合。
OrderedSet: 有序的Set,根据数据的add进行排序。
Stack: 有序集合,支持使用unshift()和shift()添加和删除。
Range(): 返回一个Seq.Indexed类型的集合,这个方法有三个参数,start表示开始值,默认值为0,end表示结束值,默认为无穷大,step代表每次增大的数值,默认为1.如果start = end,则返回空集合。
Repeat(): 返回一个vSeq.Indexe类型的集合,这个方法有两个参数,value代表需要重复的值,times代表要重复的次数,默认为无穷大。
Record: 一个用于生成Record实例的类。类似于JavaScript的Object,但是只接收特定字符串为key,具有默认值。
Seq: 序列,但是可能不能由具体的数据结构支持。
Collection: 是构建所有数据结构的基类,不可以直接构建
上面那么多常用的也就是 List和Map
五、几个重要的API 1、fromJS()
fromJS() 是最最最常用的将原生JS数据转换为ImmutableJS数据的转换方法。使用方式类似于 JSON.parse(),接收两个参数:json 数据和 reviver函数
在不传递reviver函数的情况下,默认将原生JS的Array转为List,Object转为Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ) { const isIndexed = Immutable.Iterable.isIndexed(value); return isIndexed ? value.toList() : value.toOrderedMap(); }); console .log(t2);
2、is()
先来看官网的一段话: immutable数据应该被当作值而不是对象,值是表示该事件在特定时刻的状态。这个原则对理解不可变数据的适当使用是最重要的。为了将Immutable.js数据视为值,就必须使用Immutable.is()函数或.equals()方法来确定值相等,而不是确定对象引用标识的 === 操作符
所以is()就是用来对两个immutable对象进行值比较的。使用方式类似于 Object.is(obj1, obj2),接收两个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const map1 = Immutable.Map({a :1 , b :1 , c :1 });const map2 = Immutable.Map({a :1 , b :1 , c :1 });console .log(map1 === map2); console .log(Immutable.is(map1, map2)); console .log(Immutable.is(undefined , undefined )); console .log(Immutable.is(null , undefined )); console .log(Immutable.is(null , null )); console .log(Immutable.is(NaN , NaN )); console .log(Object .is(0 , -0 ) ,Immutable.is(-0 , 0 ));
3、Map
Map 数据类型,对应原生 Object 数组。最最常用的 数据结构之一,循环时无序(orderedMap有序),对象的 key 可以是任意值。具体看下面的例子
1 2 3 4 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
4、List
List 数据类型,对应原生 Array数组。和原生数组,最大区别不存在’空位’。[, , , , ]
1 console .log(List([,,,,]).toJS());
六、API
我们主要介绍Map和 List
创建 1、通过构造函数 Map()
构造函数不常用,一般都是通过Immutable.fromJS()将一个JS原生对象转换为一个Immutable对象
2、Map()
1 2 3 4 5 6 7 8 9 10 11 12 console .log(Map ().toJS()); console .log(Map ({key : "value" }).toJS());
同Key覆盖问题
1 2 console .log(Map ([["key" , "value" ], ["key" , "value2" ], ["key1" , "value1" ]]).toJS());
3、List()
1 2 3 4 5 6 7 8 9 10 11 12 console .log(List().toJS()); console .log(List([1 ,2 ,3 ,4 ,{a :123 }]).toJS());
4、另一种方式
Map.of()
1 console .log(Map .of('key1' ,'value1' ,'key2' ,'value2' ,'key3' ,'value3' ).toJS());
List.of()
1 console .log(List.of({x :1 }, 2 , [3 ], 4 ).toJS());
判断是否是一个Map或者List 1、Map判断
判断是否是一个Map , 对原生Object不生效
1 2 console .log(Map .isMap({})); console .log(Map .isMap(Map ({})));
2、List判断
判断是否是一个List , 对原生Array不生效
1 2 console .log(List.isList([])); console .log(List.isList(List([])));
获取大小 1、size
1 2 3 4 5 6 7 console .log(List([1 ,2 ,3 ,4 ]).size);console .log(List.of(1 , 2 , 3 , 4 ).size);console .log(Map ({key : "value2" , key1 : "value1" }).size);console .log(Map .of({x :1 }, 2 , [3 ], 4 ).size);
count()
1 2 3 4 5 6 7 8 9 10 11 12 13 console .log(Immutable.fromJS({key : "value2" , key1 : "value1" }).count());console .log(Immutable.fromJS({key : 1 , key1 : 34 }).count((value, key, obj ) => { return value > 3 ; })); console .log(Immutable.fromJS([1 , 2 , 5 , 6 ]).count());console .log(Immutable.fromJS([1 , 2 , 5 , 6 ]).count((value, index, array ) => { return value > 3 ; }));
countBy()
countBy()和count()的区别就是它的返回值是一个对象。
1 2 3 4 5 6 7 8 9 console .log(Immutable.fromJS({key : 1 , key1 : 34 }).countBy((value, key, obj ) => { return value > 3 ; }).toJS()); console .log(Immutable.fromJS([1 , 2 , 5 , 6 ]).countBy((value, index, array ) => { return value > 3 ; }).toJS());
添加元素 1、Set
1 2 3 4 5 6 7 8 9 10 11 const $obj1 = Map ({a : {a1 : 34 }, b : 2 , c : 3 , d : 444 });console .log($obj1.set('a' , 0 ).toJS()); console .log($obj1.set('e' , 99 ).toJS()); const $arr1 = List([1 , 2 , 3 ]);console .log($arr1.set(-1 , 0 ).toJS()); console .log($arr1.set(4 , 0 ).toJS());
2、setIn
1 2 3 4 5 console .log(Immutable.fromJS([1 , 2 , 3 , {a : 45 , b : 64 }]).setIn(['3' , 'a' ], 1000 ).toJS());console .log(Immutable.fromJS([1 , 2 , 3 , {a : 45 , b : 64 }]).setIn(['3' , 'a' ], 1000 ).toJS());
List 特有的添加元素 1、插入元素
1 2 3 console .log(Immutable.fromJS([1 , 2 , 3 ]).insert(1 , 1.5 ).toJS());
2、设置size
1 console .log(List([]).setSize(2 ).toJS());
3、pop、push、shift、unshift
List数据类型也拥有pop、push、shift、unshift这四种操作方法,和原生Array的四种方法使用方式一致,但唯一区别就是返回新的List,并且不改变原来的数组本身,而原生则是会改变元素本身
1 2 3 4 5 6 const $test = List([1 , 2 , 3 , 4 ]);console .log($test.pop().toJS(), $test.toJS()); const test = [1 , 2 , 3 , 4 ];console .log(test.pop(), test);
4、花样插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 console .log(Immutable.fromJS([1 , 2 , 5 , 6 ]).interpose(5555 ).toJS()); console .log(Immutable.fromJS([1 , 2 , 5 , 6 ]).interleave(Immutable.fromJS([555 , 666 ])).toJS()); console .log(Immutable.fromJS([1 , 2 , 5 , 6 ]).zip(Immutable.fromJS([555 , 666 ]).toJS())); console .log(Immutable.fromJS([1 , 2 , 5 , 6 ]).zipWith((a, b ) => { return a + b; }, Immutable.fromJS([555 , 666 ]).toJS()));
删除元素 1、delete(key)
1 2 3 4 5 6 7 8 9 console .log(Immutable.fromJS([1 , 2 , 3 ]).delete(1 ).toJS(), $arr1.toJS());console .log(Immutable.fromJS([1 , 2 , 3 ]).delete(77 ).toJS(), $arr1.toJS(), '超过范围不会强制报错' );console .log(Immutable.fromJS({a : {a1 : 34 }, b : 2 , c : 3 , d : 444 }).delete('c' ).toJS(), $obj1.toJS());console .log(Immutable.fromJS({a : {a1 : 34 }, b : 2 , c : 3 , d : 444 }).delete('asdfasfd' ).toJS(), $obj1.toJS());
2、deleteIn
和 setIn使用方式一致
3、清空元素 lear()
1 2 3 4 5 console .log(Immutable.fromJS([1 , 2 , 3 ]).clear().toJS());console .log(Immutable.fromJS({a : {a1 : 34 }, b : 2 , c : 3 , d : 444 }).clear().toJS());
修改元素
修改某一个元素
1、set setIn
上面已经介绍过
2、update
update(key: K, notSetValue: V, updater: (value: V) => V): Map<K, V>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const $arr1 = Immutable.fromJS([1 , 2 , 3 ]);console .log($arr1.update('2' , (value)=> { return value * 2 ; }).toJS(), $arr1.toJS()); console .log($arr1.update('6' , 1 , (value)=> { return value * 2 ; }).toJS(), $arr1.toJS()); console .log($arr1.update('6' , 0 , (value)=> { return value * 2 ; }).toJS(), $arr1.toJS()); 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()); console .log($obj1.update('e' , 1 , (value)=> { return value * 2 ; }).toJS(), $obj1.toJS()); console .log($obj1.update('e' , 0 , (value)=> { return value * 2 ; }).toJS(), $obj1.toJS());
3、updateIn
使用方式和setIn一样。
获取某个元素值 1、get getIn
使用方式:get(key: number, notSetValue?: T)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const $test = Immutable.fromJS([1111111 , 22222 , {a : 888123 }]);console .log($test.get(0 )); console .log(Immutable.fromJS({1 : 'abc' }).get(1 ), Immutable.fromJS({1 : 'abc' }).get('1' ));console .log($test.get(11 , 'no have value' )); console .log($test.getIn(['2' , 'a' ], 'child no have value' )); console .log($test.getIn(['2' , 'b' ], 'child no have value' )); const $test = Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 });console .log($test.get('a' )); console .log($test.get('v' , 'no have value' )); console .log($test.getIn(['a' , 'a1' ], 'child no have value' )); console .log($test.getIn(['d' , 'b1' ], 'child no have value' ));
2、获取头、尾元素
1 2 3 4 5 6 7 8 9 const $arr1 = Immutable.fromJS([1 , 2 , 3 ]);console .log($arr1.first());console .log($arr1.last());Immutable.fromJS({a : {a1 : 34 }, b : 2 , c : 3 , d : 444 }); console .log($obj1.first());console .log($obj1.last());
查找某个元素 1、find() findLast()
find()、findLast()返回 value
1 2 3 4 5 6 7 8 9 console .log(Immutable.fromJS([1 , 2 , 56 , {a : {b : 111 }}]).find((value, index, array ) => { return index === 3 ; }).toJS()); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).find((value, key, obj ) => { return value === 3 ; }));
2、findKey() findLastKey()
findKey()、findLastKey() 返回 key
1 2 3 4 5 6 7 8 9 console .log(Immutable.fromJS([1 , 2 , 3 , {a : {b : 111 }}]).findKey((value, index, array ) => { return index === 3 ; })); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).findKey((value, key, obj ) => { return value === 3 ; }));
3、findEntry() findLastEntry()
findEntry()、findLastEntry() 返回 key:value
1 2 3 4 5 6 7 8 9 console .log(Immutable.fromJS([1 , 2 , 3 , {a : {b : 111 }}]).findEntry((value, index, array ) => { return index === 3 ; })); 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 })); }));
4、keyOf() lastKeyOf()
keyOf()、lastKeyOf() 根据 value 返回key。
1 2 3 4 5 6 7 / List console .log(Immutable.fromJS([1 , 2 , 3 , {a : {b : 111 }}]).keyOf(Immutable.fromJS({a : {b : 111 }}))); console .log(Immutable.fromJS([1 , 2 , 3 , {a : {b : 111 }}]).keyOf(2 )); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).keyOf(Immutable.fromJS({a1 : 222 }))); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).keyOf(2 ));
List 特有查找某个元素 1、indexOf() lastIndexOf()
1 2 console .log(Immutable.fromJS([1 , 2 , 3 , {a : {b : 111 }}]).indexOf(Immutable.fromJS({a : {b : 111 }})));
2、findIndex() findLastIndex()
1 2 3 console .log(Immutable.fromJS([1 , 2 , 3 , {a : {b : 111 }}]).findIndex((value, index, array ) => { return value/3 === 1 ; }));
查找最大、最小元素
max()、maxBy()默认比较规则为>,min()、minBy()默认比较规则为>
1、max()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 console .log(Immutable.fromJS([1 , 2 , 301 , 88 ]).max()); console .log(Immutable.fromJS([1 , 2 , 301 , 88 ]).max((valueA, valueB ) => { return valueA > valueB; })); console .log(Immutable.fromJS({a : 8888 , b : 2 , c : 3 , d : 444 }).max()); console .log(Immutable.fromJS({a : 8888 , b : 2 , c : 3 , d : 444 }).max((valueA, valueB ) => { return valueA > valueB; }));
2、maxBy()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 console .log(Immutable.fromJS([{a : 2 }, {a : 1 }, {a : 2301 }, {a : 222 }]).maxBy((value, index, array ) => { return value.get('a' ); }).toJS()); console .log(Immutable.fromJS([{a : 2 }, {a : 1 }, {a : 2301 }, {a : 222 }]).maxBy((value, index, array ) => { return value.get('a' ); }, (valueA, valueB) => { return valueA > valueB; }).toJS()); console .log(Immutable.fromJS({a : {a1 : 222 }, b : {a1 : 11 }, c : {a1 : 33 }, d : {a1 : 54654 }}).maxBy((value, key, obj ) => { return value.get('a1' ); }).toJS()); console .log(Immutable.fromJS({a : {a1 : 222 }, b : {a1 : 11 }, c : {a1 : 33 }, d : {a1 : 54654 }}).maxBy((value, key, obj ) => { return value.get('a1' ); }, (valueA, valueB) => { return valueA > valueB; }).toJS());
3、min()
4、minBy()
5、keys() values() entries()
获取ES6 Iterable 迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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); } 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); } const values = $test.values();for (let i of values) { console .log(i); } const entries = $test.entries();for (let i of entries) { console .log(i); }
截取 1、slice()
和原生Array slice()用法一致
1 2 3 4 5 6 console .log(Immutable.fromJS([1 , 2 , 3 ]).slice(0 ).toJS());console .log(Immutable.fromJS({a : {a1 : 34 }, b : 2 , c : 3 , d : 444 }).slice(0 ).toJS());console .log(Immutable.fromJS({a : {a1 : 34 }, b : 2 , c : 3 , d : 444 }).slice(1 ).toJS());
2、rest() butLast()
1 2 3 4 5 6 7 8 9 10 11 / List console .log(Immutable.fromJS([1 , {a : 1 }, 3 , 4 , 5 , 6 ]).rest().rest().toJS()); console .log(Immutable.fromJS([1 , {a : 1 }, 3 , 4 , 5 , 6 ]).butLast().toJS()); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).rest().rest().toJS()); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).butLast().toJS());
3、skip() skipLast() skipWhile() skipUntil()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 console .log(Immutable.fromJS([1 , {a : 1 }, 3 , 4 , 5 , 6 ]).skip(2 ).toJS()); console .log(Immutable.fromJS([1 , {a : 1 }, 3 , 4 , 5 , 6 ]).skipLast(2 ).toJS()); console .log(Immutable.fromJS([111 , 33 , 22 , 44 , 55 , 66 ]).skipWhile((value, index, array ) => { return value > 31 ; }).toJS()); console .log(Immutable.fromJS([32 , 33 , 40 , 44 , 55 , 66 ]).skipWhile((value, index, array ) => { return value < 39 ; }).toJS()); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).skip(2 ).toJS()); console .log(Immutable.fromJS({a : {a1 : 222 }, b : 2 , c : 3 , d : 444 }).skipLast(2 ).toJS()); console .log(Immutable.fromJS({a : 1 , b : 2 , c : 3 , d : 444 }).skipWhile((value, key, obj ) => { return value === 1 ; }).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).skipWhile((value, key, obj ) => { return value < 39 ; }).toJS());
4、take() takeLast() takeWhile() takeUntil()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 console .log(Immutable.fromJS([1 , {a : 1 }, 3 , 4 , 5 , 6 ]).take(2 ).toJS()); console .log(Immutable.fromJS([1 , {a : 1 }, 3 , 4 , 5 , 6 ]).takeLast(2 ).toJS()); console .log(Immutable.fromJS([111 , 33 , 22 , 44 , 55 , 66 ]).takeWhile((value, index, array ) => { return value > 31 ; }).toJS()); console .log(Immutable.fromJS([32 , 33 , 40 , 44 , 55 , 66 ]).takeUntil((value, index, array ) => { return value > 41 ; }).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).take(2 ).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).takeLast(2 ).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).takeWhile((value, key, obj ) => { return value > 2 ; }).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).takeUntil((value, key, obj ) => { return value > 39 ; }).toJS());
循环遍历 1、map() filter() every() some() forEach() reduce() reduceRight()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 ]).map((value, index, array )=> { return value * 2 ; }).toJS()); console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 ]).filter((value, index, array )=> { return value % 2 === 0 ; }).toJS()); console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 ]).every((value, index, array )=> { return value > 2 ; })); console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 ]).some((value, index, array )=> { return value > 2 ; })); 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 ; })); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).map((value, key, obj )=> { return value * 2 ; }).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).filter((value, key, obj )=> { return value % 2 === 0 ; }).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).every((value, key, obj )=> { return value > 2 ; })); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).some((value, key, obj )=> { return value > 2 ; })); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).forEach((value, key, obj )=> { return value < 444 ; }));
Map 特有 mapKeys() mapEntries()
对Map元素进行处理,返回处理后的对象
1 2 3 4 5 6 7 8 9 10 console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).mapKeys((key )=> { return key + 'hhh' ; }).toJS()); console .log(Immutable.fromJS({a : 5 , b : 2 , c : 3 , d : 444 }).mapEntries(([key, value] )=> { return [key + 'aaa' , value+'hhhh' ]; }).toJS());
merge merge() mergeDeep() mergeWith() mergeDeepWith()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 }}]);console .log($test.merge($test1).toJS(), $test.toJS());console .log($test.mergeDeep($test1).toJS(), $test.toJS());console .log($test.mergeWith((prev, next )=> { return prev; }, $test1).toJS(), $test1.toJS()); console .log($test.mergeDeepWith((prev, next )=> { return prev; }, $test1).toJS(), $test1.toJS()); 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 });console .log($test.merge($test1).toJS(), $test.toJS());console .log($test.mergeDeep($test1).toJS(), $test.toJS());console .log($test.mergeWith((prev, next )=> { return prev; }, $test1).toJS(), $test1.toJS()); console .log($test.mergeDeepWith((prev, next )=> { return prev; }, $test1).toJS(), $test1.toJS());
jonin() 转换为字符串
使用方式和原生Array的join()一样
1 2 3 4 console .log(Immutable.fromJS([1 , 2 , 3 , {a : 123 , b : 321 }]).join()); console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : 444 }).join());
isEmpty() 判空 1 2 3 4 console .log(Immutable.fromJS([]).isEmpty()); console .log(Immutable.fromJS({}).isEmpty());
has() hasIn() 检查是否有某个key 1 2 3 4 5 6 7 console .log(Immutable.fromJS([1 , 2 , 3 , {a : 123 , b : 321 }]).has('0' )); console .log(Immutable.fromJS([1 , 2 , 3 , {a : 123 , b : 321 }]).hasIn([3 , 'b' ])); console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : 444 }).has('a' )); console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : 444 }).hasIn(['a' , 'a3' ]));
includes() 是否包含某些元素
includes()、contains()这俩等效
1 2 3 4 5 6 7 8 9 10 11 console .log(Immutable.fromJS([6 , 5 , 4 , 3 , 2 , 1 , 89 ]).includes('89' ));console .log(Immutable.fromJS([6 , 5 , 4 , 3 , 2 , 1 , '89' ]).contains('89' ));console .log(Immutable.fromJS([6 , 5 , 4 , 3 , 2 , 1 , Immutable.fromJS([6 , 5 , 4 ])]).contains(Immutable.fromJS([6 , 5 , 4 ])));console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : 89 }).includes('89' ));console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : '89' }).contains('89' ));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 ])));
isSubset() 子集判断 1 2 3 4 5 6 7 8 9 10 11 console .log(Immutable.fromJS([6 , 5 , 1 , [6 , 5 , 4 ]]).isSubset(Immutable.fromJS([[6 , 5 , 4 ], 6 , 5 , 4 , 3 , 2 , 1 , '89' ])));console .log(Immutable.fromJS([[6 , 5 , 4 ], 6 , 5 , 4 , 3 , 2 , 1 , '89' ]).isSuperset(Immutable.fromJS([6 , 5 , 1 , [6 , 5 , 4 ]])));console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }}).isSubset(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : 5 })));console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : 5 }).isSuperset(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }})));
reverse() 反转 1 2 3 4 5 6 console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 , 6 ]).reverse().toJS());console .log(Immutable.fromJS({b : 2 , a : {a1 : 222 , a3 : 456 }, c : 3 , d : 5 }).reverse().toJS());
排序
sort()和sortBy()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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()); 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()); console .log(Immutable.fromJS({b : 2 , a : 88 , c : 3 , d : 5 }).sort().toJS());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()); 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());
flatten() 平铺
参数默认情况下,false 深度平铺,true 浅度平铺1层
1 2 3 4 5 6 7 8 9 10 11 console .log(Immutable.fromJS([1 , 2 , 3 , 4 , [1 , 11 , 111 , 12344 ], {a : 1234 , b : {bb : [777 , 888 ]}}, 5 , 6 ]).flatten().toJS());console .log(Immutable.fromJS([1 , 2 , 3 , 4 , [1 , 11 , 111 , 12344 ], {a : 1234 , b : {bb : [777 , 888 ]}}, 5 , 6 ]).flatten(true ).toJS());console .log(Immutable.fromJS({b : 2 , a : {a1 : {a5 : 333 }, a3 : [1 ,2 ,3 ]}, c : 3 , d : 5 }).flatten().toJS());console .log(Immutable.fromJS({b : 2 , a : {a1 : {a5 : 333 }, a3 : [1 ,2 ,3 ]}, c : 3 , d : 5 }).flatten(true ).toJS());
groupBy() 分组
返回值是OrderedMap
1 2 3 4 5 6 7 8 9 10 11 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()); console .log(Immutable.fromJS({b : {a5 : 333 }, a : {a5 : 333 }, c : {a5 : 334 }, d : {a5 : 334 }}).groupBy((value ) => { return value.get('a5' ) }).toJS());
flip() Map 特有翻转 1 console .log(Immutable.fromJS({b : 'b1' , a : 'a1' , c : 'c1' , d : 'd1' }).flip().toJS());
连接 concat() 1 2 3 4 5 6 7 8 9 10 11 const $test1 = Immutable.fromJS([1 , 2 , 3 , 4 , 5 , 6 ]);const $test2 = Immutable.fromJS([111 , 222 , 333 , 444 , 555 , 666 ]);console .log($test1.concat($test2).toJS()); console .log($test1.toJS(), $test2.toJS()); const $test1 = Immutable.fromJS({b : 2 , a : {a1 : {a5 : 333 }, a3 : [1 ,2 ,3 ]}, c : 3 , d : 5 });const $test2 = Immutable.fromJS({b1 : 22 , b : 34 });console .log($test1.concat($test2).toJS()); console .log($test1.toJS(), $test2.toJS());
类型转换 1、转换为原生类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 , 6 , {a : {b : [1234 , 22 ]}}]).toArray());console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 , 6 , [1234 , 22 ]]).toArray());console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 , 6 , {a : {b : [1234 , 22 ]}}]).toObject());console .log(Immutable.fromJS([1 , 2 , 3 , 4 , 5 , 6 , [1234 , 22 ]]).toObject());console .log(Immutable.fromJS({b : 2 , a : {a1 : {a5 : 333 }, a3 : [1 ,2 ,3 ]}, c : 3 , d : 5 }).toArray());console .log(Immutable.fromJS({b : 2 , a : [1 , 2 , 2 ], c : 3 , d : 5 }).toArray());console .log(Immutable.fromJS({b : 2 , a : {a1 : {a5 : 333 }, a3 : [1 ,2 ,3 ]}, c : 3 , d : 5 }).toObject());console .log(Immutable.fromJS({b : 2 , a : [1 , 2 , 2 ]}).toObject());
2、转换为其他ImmutableJS数据类型
七、和React Redux 架构的结合
利用 immutable.js 不可变的特性,可以极大的优化React render的冗余执行。React 官方提供的PureRenderMixin是浅比较
1、immutable-pure-render-decorator
专门针对immutable的PureRenderMixin,用来装饰React组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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); } 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> ) } }
2、优化shouldComponentUpdate()
我们都知道官方提供的React.addons.PureRenderMixin提供的shouldComponentUpdate(),只能进行浅比较,对于引用类型Object、Array比较无力,而如果使用Immutable的Map和List替换Object、Array,则可以使用Immutable.is()来比较两个引用类型,从而补充了React.addons.PureRenderMixin的漏洞。
3、高阶组件封装
对于使用immutable.js的项目,在应用公共组件的时候,由于公共组件的内部实现一定是原生JS数据,所以我们只能传递原生JS数据到公共组件,但是如果转换成了原生JS数据,就又会出现"React.addons.PureRenderMixin提供的shouldComponentUpdate()`是浅比较”问题,对此可以使用下面的高阶组件进行封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import {React} from 'base' ;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 } /> ; } } };
3、Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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' ;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))
八、思维导图总结API
九、更多参考
facebook.github.io/immutable-js/docs/