immutable的出现是为了性能优化,将可共享的数据提出,只更新改变的数据。减少性能消耗
immutable是什么?
官网:
https://immutable-js.github.io/immutable-js/
https://cloud.tencent.com/developer/section/1489374
- JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。如 foo={a: 1}; bar=foo; bar.a=2 你会发现此时 foo.a 也被改成了 2。虽然这样做可以节约内存,但当应用复杂后,这就造成了非常大的隐患,Mutable 带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。
什么是不可变数据
- Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
- 持久化数据结构:这里说的持久化是用来描述一种数据结构,指一个数据,在被修改时,仍然能够保持修改前的状态,即不可变类型。
结构共享:Immutable使用先进的tries(字典树)技术实现结构共享来解决性能问题,当我们对一个Immutable对象进行操作的时候,ImmutableJS会只clone该节点以及它的祖先节点,其他保持不变,这样可以共享相同的部分,大大提高性能。
惰性操作:创建的对象时其实代码块没有被执行,只是被声明了,代码在获取或修改的时候才会实际被执行
immutable.js的优缺点
优点:
- 降低mutable带来的复杂度
- 节省内存
- 并发安全
- 历史追溯性(时间旅行):时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行,想一下如果现在页面有个撤销的操作,撤销前的数据被保留了,只需要取出就行,这个特性在redux或者flux中特别有用
- 拥抱函数式编程:immutable本来就是函数式编程的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便。推荐一本函数式编程的在线免费书《JS 函数式编程指南》。
缺点:
- 需要重新学习api
- 资源包大小增加(源码5000行左右)
- 容易与原生对象混淆:由于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。
Immutable 的常用API
安装 npm i -S immutable
- Map(): 原生object转Map对象
const map1 = Map({ a: 1, b: 2, c: 3})
const map2 = Map({ a: 1, b: 2, c: 3})
console.log(map1 === map2) // false
console.log(map1.equals(map2)) // true
map.toJS() // 把map转为js对象
- List(): 原生array转List对象
const list1 = List([1, 2]);
const list2 = list1.push(3, 4, 5);
// 获取值
console.log(list2.get(0));
const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const list3 = list2.concat(list1);
console.log(list3.toArray())
- fromJS(): 原生js转immutable对象
const imState = fromJS({
name: 'lisi',
users: ['aa', 'bb']
})
- 获取数据
imState.get('name')
console.log(imState.get('users').get(1))
console.log(imState.getIn(['users', 0]))
- toJS(): immutable对象转原生js 不推荐使用
const state = imState.toJS()
console.log(state);
--------------------------------------------------------
- set/setIn/update/updateIn 修改数据
const newState = imState.set('name', 'zhangsan')
const newState = imState.setIn(['name'], 'zhangsan')
const newState = imState.update('count', value => value + 1)
const newState = imState.updateIn(['count'], value => value + 1)
redux中集成immutable.js
- 安装redux-immutable(npm i -S redux-immutable)
redux中利用combineReducers来合并reducer并初始化state,redux自带的combineReducers只支持state是原生js形式的,所以需要使用redux-immutable提供的combineReducers来替换原来的方法。 - 它的合并是支持immutable对象合并
import { combineReducers } from 'redux-immutable'
...
export default createStore(
reducer,
composeEnhancers(applyMiddleware(thunk))
)
把所有的reducer数据转换为immutable对象
import {fromJS} from 'immutable'
const defaultState = fromJS({})
在组件中使用react-redux取数据时,要时刻注意它是一个immutable对象
import React, { Component } from 'react';
import { connect } from 'react-redux'
@connect(state => {
// return state.toJS()
// console.log(state);
return {
username: state.getIn(['user', 'username']),
users: state.getIn(['user', 'users'])
}
}, null)
class App extends Component {
render() {
// console.log(this.props.users);
return (
<div>
App--- {this.props.username}
<hr />
{
this.props.users.map(item => (
<li>{item.get('name')}</li>
))
}
</div>
);
}
}
export default App;