有人说 Immutable 能够给 React 应用带来数十倍的提高,也有人说 Immutable 的引入是近期 JavaScript 中伟大的发明,由于同期 React 太火,它的光芒被掩盖了。这些至少说明 Immutable 是颇有价值的,下面咱们来一探究竟。前端
Immutable是一旦建立,就不能被更改的数据。对Immutable对象的任何修改或添加删除操做都会返回一个新的Immutable对象。Immutable实现的原理是Persistent Data Structure(持久化数据结构),也就是是永久数据建立新数据时,要保证旧数据同时可用且不变。同时为了不deepCopy把全部节点都复制一遍带来的性能损耗,Immutable使用了Structural Sharing(结构共享),即若是对象树结点发生变化,只修改这个结点和受它影响的父节点,其余结点进行共享。react
初识:git
让咱们看下面一段代码:github
function keyLog(touchFn) { let data = { key: 'value' }; fun(data); console.log(data.key); // 猜猜会打印什么? }
不查看fun方法,不知道它对data作了什么,没法确认会打印什么。但若是data是Immutable,你能够肯定打印的就是value:算法
function keyLog(touchFn) { let data = Immutable.Map({ key: 'value' }); fun(data); console.log(data.get('key')); // value }
JavaScript中的Object与Array等使用的是引用赋值,若是新的对象简单的引用了原始对象,改变新的对象也将影响旧的。编程
foo = {a:1}; bar = foo; bar.a = 2; foo.a // 2
虽然这样能够节约内存,但当应用复杂后,形成了状态不可控,是很大的隐患,节约内存的优势变得得不偿失。redux
Immutable则不同,相应的:数组
foo = Immutable.Map({ a: 1}); bar = foo.set('a', 2); foo.get('a') // 1
简洁:安全
在redux中,它的最优作法是每一个reducer都返回一个新的对象(数组),因此咱们经常会看到这样的代码:性能优化
// reducer ... return [ ...oldArr.slice(0,3), newValue, ...oldArr.slice(4) ];
为了返回新的对象(数组),不得不有上面奇怪的样子,而在使用更深的数据结构时会变的更棘手。
让咱们看看Immutable的作法:
// reducer ... return oldArr.set(4, newValue);
是否是很简洁?
关于"===":
众所周知,对于Object与Array的===比较,是对引用地址的比较,而不是“值比较”,如:
{a:1, b:2, c:3} === {a:1, b:2, c:3}; // false [1, 2, [3, 4]] === [1, 2, [3, 4]]; // false
对于上面只能采用deepCopy、deepCompare来比较,不只麻烦并且耗性能。
咱们感觉一下Immutbale的作法:
map1 = Immutable.Map({a:1, b:2, c:3}); map2 = Immutable.Map({a:1, b:2, c:3}); Immutable.is(map1, map2); // true 比较值
map1 === map2; // false 比较地址
// List1 = Immutable.List([1, 2, Immutable.List[3, 4]]); List1 = Immutable.fromJS([1, 2, [3, 4]]); List2 = Immutable.fromJS([1, 2, [3, 4]]); Immutable.is(List1, List2); // true
彷佛有阵清风吹过。
Immutable.is
比较的是两个对象的 hashCode
或 valueOf
(对于 JavaScript 对象)。因为 immutable 内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode
相等,值就是同样的。这样的算法避免了深度遍历比较,性能很是好。
Immutable使用了Structure Sharing会尽可能复用内存,甚至之前使用的对象也能够再次被复用,引用的对象会被垃圾回收。
import { Map} from 'immutable'; let a = Map({ select: 'users', filter: Map({ name: 'Cam' }) }) let b = a.set('select', 'people'); a === b; // false a.get('filter') === b.get('filter'); // true
上面 a 和 b 共享了没有变化的 filter
节点。
并发安全:
传统的并发很是难作,由于要处理各类数据不一致问题,所以『聪明人』发明了各类锁来解决。但使用了 Immutable 以后,数据天生是不可变的,并发锁就不须要了。
然而如今并没什么卵用,由于 JavaScript 仍是单线程运行的啊。但将来可能会加入,提早解决将来的问题不也挺好吗?
函数式编程:
Immutable自己就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。由于只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。
像 ClojureScript,Elm 等函数式编程语言中的数据类型天生都是 Immutable 的,这也是为何 ClojureScript 基于 React 的框架 --- Om 性能比 React 还要好的缘由。
熟悉React的都知道,React作性能优化时有个大招,就是使用shouldComponentUpdate(),但它默认返回true,即始终会执行render()方法,后面作Virtual DOM比较,并得出是都须要作真是DOM更新,这里每每会带来不少务必要的渲染成为性能瓶颈。
在使用原生属性时,为了得出shouldComponetUpdate正确的true or false,不得不用deepCopy、deepCompare来算出答案,但 deepCopy 和 deepCompare 通常都是很是耗性能的。
而在有了Immutable以后,Immutable 则提供了简洁高效的判断数据是否变化的方法,来减小 React 重复渲染,提升性能,只需 ===
和 is
比较就能知道是否须要执行 render()
,而这个操做几乎 0 成本,因此能够极大提升性能。修改后的 shouldComponentUpdate
是这样的:
import { is } from 'immutable'; shouldComponentUpdate: (nextProps = {}, nextState = {}) => { const thisProps = this.props || {}, thisState = this.state || {}; if (Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length) { return true; } for (const key in nextProps) { if (!is(thisProps[key], nextProps[key])) { return true; } } for (const key in nextState) { if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) { return true; } } return false; }
使用 Immutable 后,以下图,当红色节点的 state 变化后,不会再渲染树中的全部节点,而是只渲染图中绿色的部分:
React 建议把 this.state
看成 Immutable 的,由于修改前须要作一个 deepCopy,显得麻烦:
import '_' from 'lodash'; const Component = React.createClass({ getInitialState() { return { data: { times: 0 } } }, handleAdd() { let data = _.cloneDeep(this.state.data); data.times = data.times + 1; this.setState({ data: data }); // 若是上面不作 cloneDeep,下面打印的结果会是已经加 1 后的值。 console.log(this.state.data.times); } }
使用 Immutable 后:
getInitialState() { return { data: Map({ times: 0 }) } }, handleAdd() { this.setState({ data: this.state.data.update('times', v => v + 1) }); // 这时的 times 并不会改变 console.log(this.state.data.get('times')); }
上面的 handleAdd
能够简写成:
handleAdd() { this.setState(({data}) => ({ data: data.update('times', v => v + 1) }) }); }
目标:将state
-> Immutable化。
关键的库:gajus/redux-immutable
将原来 Redux提供的combineReducers改由上面的库提供:
// rootReduers.js // import { combineReducers } from 'redux'; // 旧的方法 import { combineReducers } from 'redux-immutable'; // 新的方法 import prop1 from './prop1'; import prop2 from './prop2'; import prop3 from './prop3'; const rootReducer = combineReducers({ prop1, prop2, prop3, }); // store.js // 建立store的方法和常规同样 import { createStore } from 'redux'; import rootReducer from './reducers'; const store = createStore(rootReducer); export default store;
经过新的combineReducers
将把store对象转化成Immutable,在container中使用时也会略有不一样(但这正是咱们想要的):
const mapStateToProps = (state) => ({ prop1: state.get('prop1'), prop2: state.get('prop2'), prop3: state.get('prop3'), next: state.get('next'), }); export default connect(mapStateToProps)(App);
Immutable 能够给应用带来极大的性能提高,可是否使用还要看项目状况。因为侵入性较强,新项目引入比较容易,老项目迁移须要评估迁移。对于一些提供给外部使用的公共组件,最好不要把 Immutable 对象直接暴露在对外接口中。
若是 JS 原生 Immutable 类型会不会太美,被称为 React API 终结者的 Sebastian Markbåge 有一个这样的提案,可否经过如今还不肯定。不过能够确定的是 Immutable 会被愈来愈多的项目使用。