整理于互联网
一、前言
从问题说起:熟悉
React组件生命周期的话都知道:调用setState方法总是会触发render方法从而进行vdom re-render相关逻辑,哪怕实际上你没有更改到Component.state
this.state = {count: 0} |
- 为了避免这种性能上的浪费,
React提供了一个shouldComponentUpdate来控制触发vdom re-render逻辑的条件。于是PureRenderMixin作为一种优化技巧被使用。它仅仅是浅比较对象,深层次的数据结构根本不管用
js中的Immutable Data
在
javascript中我们可以通过deep clone来模拟Immutable Data,就是每次对数据进行操作,新对数据进行deep clone出一个新数据
- deep clone
- 当然你或许意识到了,这样非常的慢
; |
这时候 immutableJS 就派得上用场了
var map1 = Immutable.fromJS({a:1, b:1, c:{b:{c:{d:{e:7}}}}}); |
- 遍历对象不再用
for-in,可以这样:
Immutable.fromJS({a:1, b:2, c:3}).map(function(value, key) { /* do some thing */}); |
二、什么是 Immutable Data
Immutable Data就是一旦创建,就不能再被更改的数据。对Immutable对象的任何修改或添加删除操作都会返回一个新的Immutable对象Immutable实现的原理是Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变- 同时为了避免
deepCopy把所有节点都复制一遍带来的性能损耗,Immutable使用了Structural Sharing····(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
打印
immutableJS看看有什么东西

一个说明不可变的例子
// 原生对象 |
三、为什么要在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会尽量复用内存,甚至以前使用的对象也可以再次被复用。没有被引用的对象会被垃圾回收
import { Map} from 'immutable'; |
- 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
// 常见 |
2、is()
先来看官网的一段话:
immutable数据应该被当作值而不是对象,值是表示该事件在特定时刻的状态。这个原则对理解不可变数据的适当使用是最重要的。为了将Immutable.js数据视为值,就必须使用Immutable.is()函数或.equals()方法来确定值相等,而不是确定对象引用标识的===操作符
- 所以
is()就是用来对两个immutable对象进行值比较的。使用方式类似于Object.is(obj1, obj2),接收两个参数
const map1 = Immutable.Map({a:1, b:1, c:1}); |
3、Map
Map数据类型,对应原生Object数组。最最常用的 数据结构之一,循环时无序(orderedMap有序),对象的key可以是任意值。具体看下面的例子
console.log(Map().set(List.of(1), 'list-of-one').get(List.of(1))); |
- 简单介绍 OrderedMap
OrderedMap是Map的变体,它除了具有Map的特性外,还具有顺序性,当开发者遍历OrderedMap的实例时,遍历顺序为该实例中元素的声明、添加顺序。OrderedMap比非有序Map更昂贵,并且可能消耗更多的内存。如果真要求遍历有序,请使用List
4、List
List数据类型,对应原生Array数组。和原生数组,最大区别不存在’空位’。[, , , , ]
console.log(List([,,,,]).toJS());// [undefined, undefined, undefined, undefined] |
六、API
我们主要介绍
Map和List
创建
1、通过构造函数 Map()
构造函数不常用,一般都是通过
Immutable.fromJS()将一个JS原生对象转换为一个Immutable对象
2、Map()
/* |
同Key覆盖问题
//最后的{key: value2} 覆盖了前面的 {key: value} |
3、List()
/* |
4、另一种方式
Map.of()
console.log(Map.of('key1','value1','key2','value2','key3','value3').toJS()); // {key1: "value1", key2: "value2", key3: "value3"} |
List.of()
console.log(List.of({x:1}, 2, [3], 4).toJS()); // [{x:1}, 2, [3], 4] |
判断是否是一个Map或者List
1、Map判断
- 判断是否是一个
Map, 对原生Object不生效
console.log(Map.isMap({})); // false |
2、List判断
判断是否是一个
List, 对原生Array不生效
console.log(List.isList([])); // false |
获取大小
1、size
// list |
count()
// map |
countBy()
countBy()和count()的区别就是它的返回值是一个对象。
// Map |
添加元素
1、Set
// Map |
2、setIn
// Map |
List 特有的添加元素
1、插入元素
// insert(index: number, value: T) |
2、设置size
- 默认值
undefined
console.log(List([]).setSize(2).toJS()); // [undefined, undefined] |
3、pop、push、shift、unshift
List数据类型也拥有pop、push、shift、unshift这四种操作方法,和原生Array的四种方法使用方式一致,但唯一区别就是返回新的List,并且不改变原来的数组本身,而原生则是会改变元素本身
// ImmutableJS:返回新的List,并且不改变元素本身 |
4、花样插入
// interpose |
删除元素
1、delete(key)
// List |
2、deleteIn
和
setIn使用方式一致
3、清空元素 lear()
// List |
修改元素
修改某一个元素
1、set setIn
上面已经介绍过
2、update
update(key: K, notSetValue: V, updater: (value: V) => V): Map<K, V>
// List |
3、updateIn
使用方式和setIn一样。
获取某个元素值
1、get getIn
- 使用方式:
get(key: number, notSetValue?: T)
// List |
2、获取头、尾元素
// List |
查找某个元素
1、find() findLast()
find()、findLast()返回value
// List |
2、findKey() findLastKey()
findKey()、findLastKey()返回key
// List |
3、findEntry() findLastEntry()
findEntry()、findLastEntry()返回key:value
// List |
4、keyOf() lastKeyOf()
keyOf()、lastKeyOf()根据value返回key。
/ List |
List 特有查找某个元素
1、indexOf() lastIndexOf()
// 找不到 返回 -1 |
2、findIndex() findLastIndex()
console.log(Immutable.fromJS([1, 2, 3, {a: {b: 111}}]).findIndex((value, index, array) => { |
查找最大、最小元素
max()、maxBy()默认比较规则为>,min()、minBy()默认比较规则为>
1、max()
// List |
2、maxBy()
// List |
3、min()
- 同
max()
4、minBy()
- 同
maxBy()
5、keys() values() entries()
获取
ES6 Iterable迭代器
// List |
截取
1、slice()
和原生
Array slice()用法一致
// List |
2、rest() butLast()
/ List |
3、skip() skipLast() skipWhile() skipUntil()
// List |
4、take() takeLast() takeWhile() takeUntil()
// List |
循环遍历
1、map() filter() every() some() forEach() reduce() reduceRight()
// List |
Map 特有 mapKeys() mapEntries()
对
Map元素进行处理,返回处理后的对象
//mapKeys() 返回对象 |
merge
merge() mergeDeep() mergeWith() mergeDeepWith()
// List |
jonin() 转换为字符串
使用方式和原生
Array的join()一样
// List |
isEmpty() 判空
// 判断空List |
has() hasIn() 检查是否有某个key
// List |
includes() 是否包含某些元素
includes()、contains()这俩等效
// List |
isSubset() 子集判断
// List |
reverse() 反转
// List |
排序
sort()和sortBy()
// List |
flatten() 平铺
参数默认情况下,
false深度平铺,true浅度平铺1层
// List |
groupBy() 分组
返回值是
OrderedMap
// List |
flip() Map 特有翻转
console.log(Immutable.fromJS({b: 'b1', a: 'a1', c: 'c1', d: 'd1'}).flip().toJS()); // {b1: "b", a1: "a", c1: "c", d1: "d"} |
连接 concat()
// List |
类型转换
1、转换为原生类型
// List |
2、转换为其他ImmutableJS数据类型
// toMap() |
七、和React Redux 架构的结合
利用
immutable.js不可变的特性,可以极大的优化React render的冗余执行。React官方提供的PureRenderMixin是浅比较
1、immutable-pure-render-decorator
专门针对
immutable的PureRenderMixin,用来装饰React组件
import {React} from 'base'; |
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()`是浅比较”问题,对此可以使用下面的高阶组件进行封装
import {React} from 'base'; |
3、Demo
import {React} from 'base'; |
八、思维导图总结API
九、更多参考
- facebook.github.io/immutable-js/docs/