- Immutable-js
- 这个库的实现是深拷贝还是浅拷贝?
- immutable 来源
- immutable.js三大特性:
- 持久化数据结构
- 结构共享
- 惰性操作
- Immutable.js 的几种数据类型
- immutable 使用
- 使用 npm 安装 immutable:
- 常用API介绍
- Map
- List
- List.isList() 和 Map.isMap()
- set() / setIn()
- get() / getIn()
- size
- includes()
- first() 、 last()
- is()
- merge , concat
- concat()
- updateIn() 回调函数更新 – 用法参考setIn()
- toJS()
- fromJS()
- 总结:增删改查(所有操作都会返回新的值,不会修改原来值)
- Immutable-js 优缺点
- 优点:
- 降低mutable带来的复杂度
- 节省内存
- 历史追溯性(时间旅行)
- 缺点:
- 需要重新学习api
- 资源包大小增加(源码5000行左右)
- 容易与原生对象混淆:由于api与原生不同,混用的话容易出错。
- 优点:
Immutable-js
Immutable.js,
github地址:https://github.com/immutable-js/immutable-js
每次修改一个 Immutable 对象时都会创建一个新的不可变的对象
,在新对象上操作并不会影响到原对象的数据
这个库的实现是深拷贝还是浅拷贝?
Immutable ([ɪˈmjuːtəbl] 不变的;不可变的;不能变的) 是 Facebook 开发的不可变数据集合
。
不可变数据一旦创建就不能被修改,使得应用开发更简单,允许使用函数式编程技术,比如惰性评估。
Immutable JS 提供一个惰性 Sequence,允许高效的队列方法链,类似 map 和 filter ,不用创建中间代表。
immutable 通过惰性队列
和哈希映射
提供 Sequence, Range, Repeat, Map, OrderedMap, Set 和一个稀疏 Vector。
immutable 来源
Immutable.js出自Facebook,是最流行的不可变数据结构的实现之一。
它实现了**完全的
持久化
数据结构
**,通过使用像tries这样的先进技术来实现结构共享。
所有的更新操作都会返回新的值
,但是在内部结构是共享
的,来减少内存占用(和垃圾回收的失效)。
immutable.js三大特性:
- Persistent data structure (持久化数据结构)
- structural sharing (结构共享)
- support lazy operation (惰性操作)
持久化数据结构
这里说的持久化是用来描述一种数据结构
,
指一个数据,在被修改时,仍然能够保持修改前的状态,即不可变类型。
immutable.js提供了十余种
不可变的类型(List,Map,Set,Seq,Collection,Range等)
结构共享
Immutable使用先进的tries(字典树)技术
实现结构共享来解决性能问题.
当我们对一个Immutable对象进行操作的时候
ImmutableJS会只clone该节点以及它的祖先节点
,其他保持不变,这样可以共享相同的部分,大大提高性能。
惰性操作
const oddSquares = Immutable.seq.of(1, 2, 3, 4, 5, 6, 7, 8)
.filter(item => {
console.log('immutable对象的filter执行');
return item % 2;
})
.map(x => x * x);
console.log(oddSquares.get(1)); // 9
const jsSquares = [1, 2, 3, 4, 5, 6, 7, 8]
.filter(item => {
console.log('原生数组的filter执行');
return item % 2;
})
.map(x => x * x);
console.log(jsSquares[1]); // 9
用seq创建的对象,其实代码块没有被执行,只是被声明了,
代码在get(1)的时候才会实际被执行,取到index=1的数之后,后面的就不会再执行了,
所以在filter时,第三次就取到了要的数,从4-8都不会再执行。
如果在实际业务中,数据量非常大,一个array的长度是几百,要操作这样一个array,如果应用惰性操作的特性,会节省非常多的性能。
Immutable.js 的几种数据类型
List: 有序索引集
,类似JavaScript中的Array。Map: 无序索引集
,类似JavaScript中的Object。OrderedMap: 有序的Map
,根据数据的set()进行排序。Set: 没有重复值的集合
。OrderedSet: 有序的Set
,根据数据的add进行排序。Stack: 有序集合
,支持使用unshift()和shift()添加和删除。Record: 一个用于生成Record实例的类
。类似于JavaScript的Object,但是只接收特定字符串为key,具有默认值。Seq: 序列
,但是可能不能由具体的数据结构支持。Collection: 是构建所有数据结构的基类
,不可以直接构建。
用的最多就是List
和Map
这两种数据类型。
immutable 使用
使用 npm 安装 immutable:
npm install immutable
```js
每个模块都要包括:
```js
var Immutable = require('immutable');
var map = Immutable.Map({a:1, b:2, c:3});
常用API介绍
Map
Map(): 原生object转Map对象 (只会转换第一层,注意和fromJS区别)
作用:用来创建一个新的Map对象
代码实现:
var obj = { name: 'demo', age: 100 }
var oldImmutableObj = Map(obj)
console.log(oldImmutableObj)
//Map
Immutable.Map(); // 空Map
Immutable.Map({ a: '1', b: '2' });
List
List(): 原生array转List对象 (只会转换第一层,注意和fromJS区别)
作用:用来创建一个新的List对象
代码实现:
const { List } = require('immutable');
const list1 = List([ 1, 2 ]);
const list2 = list1.push(3, 4, 5);
const list3 = list2.unshift(0);
const list4 = list1.concat(list2, list3);
assert.equal(list1.size, 2);
assert.equal(list2.size, 5);
assert.equal(list3.size, 6);
assert.equal(list4.size, 13);
assert.equal(list4.get(0), 1);
push
, set
, unshift
or splice
都可以直接用,返回一个新的immutable对象
List.isList() 和 Map.isMap()
作用:判断一个数据结构是不是List/Map类型
用法:
List.isList([]); // false
List.isList(List()); // true
Map.isMap({}) // false
Map.isMap(Map()) // true
set() / setIn()
set()修改immutalble的值:设置第一层key、index的值
用法:
var NewImmutableObj = oldImmutableObj.set('name' , 'demo2')
const originalList = List([ 0 ]);
// List [ 0 ]
originalList.set(1, 1);
// List [ 0, 1 ]
originalList.set(0, 'overwritten');
// List [ "overwritten" ]
originalList.set(2, 2);
// List [ 0, undefined, 2 ]
const originalMap = Map()
const newerMap = originalMap.set('key', 'value')
const newestMap = newerMap.set('key', 'newer value')
List在使用的时候,将index为number值设置为value。
Map在使用的时候,将key的值设置为value。
在List中使用时,若传入的number为负数,则将index为size+index的值设置为value,
例,若传入-1,则将size-1的值设为value。
若传入的number的值超过了List的长度,则将List自动补全为传入的number的值,将number设置为value,其余用undefined补全。
注:跟js中不同,List中不存在空位,[,],List中若没有值,则为undefined。
setIn()修改immutalble的值:设置深层结构中某属性的值
用法:
const originalMap = Map({
subObject: Map({
subKey: 'subvalue',
subSubObject: Map({
subSubKey: 'subSubValue'
})
})
})
const newMap = originalMap.setIn(['subObject', 'subKey'], 'ha ha!')
// Map {
// "subObject": Map {
// "subKey": "ha ha!",
// "subSubObject": Map { "subSubKey": "subSubValue" }
// }
// }
const newerMap = originalMap.setIn(
['subObject', 'subSubObject', 'subSubKey'],
'ha ha ha!'
)
// Map {
// "subObject": Map {
// "subKey": "subvalue",
// "subSubObject": Map { "subSubKey": "ha ha ha!" }
// }
// }
用法与set()一样,只是第一个参数是一个数组,代表要设置的属性所在的位置
get() / getIn()
get()获取immutalble 获取一级属性
的值
//获取List索引的元素
ImmutableData.get(0);
// 获取Map对应key的value
ImmutableData.get('a');
oldImmuObj.get("name")
newImmuObj.get("name")
案例
import React, { Component } from 'react'
import { Map } from 'immutable'
export default class App extends Component {
state = {
info: Map({
name: 'demo',
age: 100
})
}
render() {
return (
<div>
this.state.info.get('name') -- {this.state.info.get('name')} <br/>
this.state.info.get('age') -- {this.state.info.get('age')} <br/>
<button onClick={()=>{
this.setState({
info: this.state.info.set('name','32333').set('age', 1000)
})
}}>修改</button>
</div>
)
}
}
getIn()获取immutalble 获取多级属性
的值
// 获取嵌套数组中的数据
ImmutableData.getIn([1, 2]);
// 获取嵌套map的数据
ImmutableData.getIn(['a', 'b']);
const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
多重对象情况
import React, { Component } from 'react'
import { Map } from 'immutable'
export default class App extends Component {
state = {
info: Map({
name: 'demo',
select: 'aa',
fliter: Map({
text: '',
up: true,
down: false
})
})
}
componentDidMount() {
// console.log(this.state.info)
}
render() {
return (
<div>
<button onClick={() => {
this.setState({
info: this.state.info.set('name', 'dsdsdd')
})
}}>change</button> <br />
this.state.info.get('name') -- {this.state.info.get('name')} <br />
<Child fliter={this.state.info.get('filter')}></Child
</div>
)
}
}
class Child extends Component {
shouldComponentUpdate(nextProps, nextState) {
if (this.props.filter === nextProps.filter) { // 注意此处:判断是否变化
return false
}
return true
}
render() {
return (
<div>Child -- {this.props.fliter}</div>
)
}
componentDidUpdate() {
console.log('componentDidUpdate')
}
}
size
作用:属性,获取List/Map的长度,等同于ImmutableData.count();
用法:
// 查看List或者map大小
immutableData.size
或者
immutableData.count()
```js
### has() 、 hasIn()
**作用**:判断是否存在某一个key
**用法**:
```js
Immutable.fromJS([1,2,3,{a:4,b:5}]).has('0'); //true
Immutable.fromJS([1,2,3,{a:4,b:5}]).has('0'); //true
Immutable.fromJS([1,2,3,{a:4,b:5}]).hasIn([3,'b']) //true
includes()
作用:判断是否存在某一个value
用法:
Immutable.fromJS([1,2,3,{a:4,b:5}]).includes(2); //true
Immutable.fromJS([1,2,3,{a:4,b:5}]).includes('2'); //false 不包含字符2
Immutable.fromJS([1,2,3,{a:4,b:5}]).includes(5); //false
Immutable.fromJS([1,2,3,{a:4,b:5}]).includes({a:4,b:5}) //false
Immutable.fromJS([1,2,3,{a:4,b:5}]).includes(Immutable.fromJS({a:4,b:5})) //true
first() 、 last()
作用:用来获取第一个元素或者最后一个元素,若没有则返回undefined
代码:
Immutable.fromJS([1,2,3,{a:4,b:5}]).first()//1
Immutable.fromJS([1,2,3,{a:4,b:5}]).last()//{a:4,b:5}
Immutable.fromJS({a:1,b:2,c:{d:3,e:4}}).first() //1
Immutable.fromJS({a:1,b:2,c:{d:3,e:4}}).first() //{d:3,e:4}
is()
作用:对两个对象进行比较
用法:is(map1,map2)
简介:
和js中对象的比较不同,在js中比较两个对象比较的是地址,
但是在Immutable中比较的是这个对象hashCode和valueOf,只要两个对象的hashCode相等,值就是相同的,避免了深度遍历,提高了性能
代码实现:
import { Map, is } from 'immutable'
const map1 = Map({ a: 1, b: 1, c: 1 })
const map2 = Map({ a: 1, b: 1, c: 1 })
map1 === map2 //false
Object.is(map1, map2) // false
is(map1, map2) // true
// is(): 判断两个immutable对象是否相等
const objA = { name: 'graceji', age: 18 };
const objB = { name: 'graceji', age: 18 };
const imA = immutable.Map({ name: 'graceji', age: 18 });
const imB =immutable.Map({ name: 'graceji', age: 18 });
objsA === objB // false; 比较的是地址
immutable.is(imA, imB); // true; hashcode相同
merge , concat
merge
作用:浅合并,新数据与旧数据对比,旧数据中不存在的属性直接添加,就数据中已存在的属性用新数据中的覆盖
mergrWith
作用:自定义浅合并,可自行设置某些属性的值
mergeIn
作用:对深层数据进行浅合并
mergeDeep
作用:深合并,新旧数据中同时存在的的属性为新旧数据合并之后的数据
mergeDeepIn
作用:对深层数据进行深合并
mergrDeepWith
作用:自定义深合并,可自行设置某些属性的值 ’
代码实现:
const { Map, List } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3, d: 4 });
const map2 = Map({ c: 10, a: 20, t: 30 });
const obj = { d: 100, o: 200, g: 300 };
const map3 = map1.merge(map2, obj);
// Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
这里用一段示例彻底搞懂merge,此示例为Map结构,List与Map原理相同
const Map1 = Immutable.fromJS({a:111,b:222,c:{d:333,e:444}});
const Map2 = Immutable.fromJS({a:111,b:222,c:{e:444,f:555}});
const Map3 = Map1.merge(Map2);
//Map {a:111,b:222,c:{e:444,f:555}}
const Map4 = Map1.mergeDeep(Map2);
//Map {a:111,b:222,c:{d:333,e:444,f:555}}
const Map5 = Map1.mergeWith((oldData,newData,key)=>{
if(key === 'a'){
return 666;
}else{
return newData
}
},Map2);
//Map {a:666,b:222,c:{e:444,f:555}}
concat()
作用:对象的拼接,用法与js数组中的concat()相同,返回一个新的对象。
用法:const List = list1.concat(list2)
代码实现:
const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const array = [ 7, 8, 9 ];
const list3 = list1.concat(list2, array);
// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
updateIn() 回调函数更新 – 用法参考setIn()
//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 } } }
toJS()
toJS(): immutable数据转原生js类型的数据 (深度转换,会将内部嵌套的Map和List全部转换成原生js)
用法:value.toJS()
代码实现:
const { Map, List } = require('immutable');
const deep = Map({ a: 1, b: 2, c: List([ 3, 4, 5 ]) });
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]}'
toJS() mmutable===>普通对象
oldImmuObj.toJS()
newImmuObj.toJS()
案例
import React, { Component } from 'react'
import { Map } from 'immutable'
export default class App extends Component {
state = {
info: {
name: 'demo',
age: 100
}
}
render() {
return (
<div>
this.state.info.name -- {this.state.info.name} <br/>
this.state.info.age -- {this.state.info.age} <br/>
<button onClick={()=>{
var old = Map(this.state.info)
var newData = old.set('name','32333').set('age', 1000)
this.setState({
info: newData.toJS()
})
}}>修改</button>
</div>
)
}
}
fromJS()
fromJS(): 原生js转immutable对象 (深度转换,会将内部嵌套的对象和数组全部转成immutable)
用法:fromJS(value, converter)
简介:value是要转变的数据,converter是要做的操作。第二个参数可不填,默认情况会将数组准换为List类型,将对象转换为Map类型,其余不做操作
const { fromJS } = require('immutable');
const nested = fromJS({ a: { b: { c: [ 3, 4, 5 ] } } });
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
const obj = fromJS({a:'123',b:'234'},function (key, value, path) {
console.log(key, value, path)
return isIndexed(value) ? value.toList() : value.toOrderedMap())
})
总结:增删改查(所有操作都会返回新的值,不会修改原来值)
代码实现
// 增删改查(所有操作都会返回新的值,不会修改原来值)
const immutableData = immutable.fromJS({
a: 1,
b: 2,
c: {
d: 3
}
});
const data1 = immutableData.get('a') // data1 = 1
const data2 = immutableData.getIn(['c', 'd']) // data2 = 3; getIn用于深层结构访问
const data3 = immutableData.set('a' , 2); // data3中的 a = 2
const data4 = immutableData.setIn(['c', 'd'], 4); // data4中的 d = 4
const data5 = immutableData.update('a' , function(x) { return x+4 }) // data5中的 a = 5
const data6 = immutableData.updateIn(['c', 'd'], function(x) { return x+4 }) // data6中的 d = 7
const data7 = immutableData.delete('a') // data7中的 a 不存在
const data8 = immutableData.deleteIn(['c', 'd']) // data8中的 d 不存在
案例
import React, { Component } from 'react'
import { fromJS } from 'immutable'
export default class App extends Component {
// state = {
// info:Map({
// name:"demo",
// location:Map({
// province:"辽宁",
// city:"大连"
// }),
// favor:List(["读书","看报","写代码"])
// })
// }
// 使用 fromJS 转换成 immutable对象
state = {
info:fromJS({ // 注意这里 fromJS -- 转换成 immutable对象
name:"demo",
location:{
province:"辽宁",
city:"大连"
},
favor:["读书","看报","写代码"]
})
}
render() {
return (
<div>
<h1>个人信息修改</h1>
<div>
{this.state.info.get('name')} <button onClick={()=>{
this.setState({
info: this.state.info.set('name','demo33')
.setIn(['location','city'],'wuhan') // 注意这里 setIn(['location','city'],'wuhan')
})
}}>修改</button>
</div>
<div>
{this.state.info.get('location').get('province')} -- {this.state.info.get('location').get('city')}
</div>
<ul>
{
this.state.info.get('favor').map((item,index)=><li key={index}>{item} <button onClick={()=>{
this.setState({
info: this.state.info
.updateIn(["favor"],(list)=>list.splice(index,1)) // 注意这里 updateIn(["favor"],(list)=>list.splice(index,1))
})
}}>删除</button></li>)
}
</ul>
</div>
)
}
}
Immutable-js 优缺点
优点:
降低mutable带来的复杂度
节省内存
历史追溯性(时间旅行)
时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行。
如果现在页面有个撤销的操作,撤销前的数据被保留了,只需要取出就行,这个特性在redux或者flux中特别有用
- 拥抱函数式编程:immutable本来就是
函数式编程
的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便