React杂谈之Immutable.js
Immutable.js是什么,有何意义
immutable
从英文翻译过来叫做不可改变的
,所以Immutable.js
就是用来生成不可变的数据的。
Javascript的缺陷
众所周知在javascript中,对象一般是可变的,如下:1
2
3
4let foo={a: 1};
let bar=foo;
bar.a=2;
console.log(foo.a) // 2
这里可以看出foo的a属性其实也跟着bar中的a属性发生了改变。
Immutable的定义
这里就直接引用官方的原话了:
Immutable 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
那如果通过immutable来对对象进行操作会怎样呢,下面来看代码以便于更好地理解上面一段话:1
2
3
4import Immutable from 'immutable';
let foo = Immutable.Object({a:1});
let bar = foo.set('a',2); // 定义一个bar变量,并且将一个新的Immutable返回给它
console.log(foo.get('a')); // 获取foo对象(Immutable)的a属性,结果是1
从上面的代码可以看出虽然赋值给bar的时候对foo进行了修改操作,但是foo中的属性值并没有发生变化,这也就印证了上述所说‘Immutable 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象’的这句话。
Immutable的意义
那么说了这么多,这个Immutable的意义究竟在哪里。这里主要分为以下几点:
Immutable 降低了 Mutable 带来的复杂度
可变(Mutable)数据耦合了 Time 和 Value 的概念,造成了数据很难被回溯。
1
2
3
4
5function touchAndLog(touchFn) {
let data = { key: 'value' };
touchFn(data);
console.log(data.key);
}在上述代码中你不可能知道到底会打印出什么,因为我们无法确定touchFn对data到底做了哪些操作。那如果这个data是个Immutable类型的数据呢,毫无疑问打印出来是
value
。节省内存
Immutable.js 使用了 Structure Sharing 会尽量复用内存,甚至以前使用的对象也可以再次被复用。没有被引用的对象会被垃圾回收。
可以做到对于数据应用的时间旅行等功能
因为每次数据都是不一样的,只要把这些数据放到一个数组里储存起来,想回退到哪里就拿出对应数据即可,很容易开发出撤销重做这种功能。
函数式编程
Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。
与React全家桶摩擦出的火花
在React中使用Immutable的好处
其实下面这张图更方便理解:
当数据发生改变的时候,只有关联节点会被修改,其他节点则会被复制一份,最终产生一个新的数据树;
由于react中我们只要执行了this.setState()方法,不管state的值是否发生变化都会重新执行一遍render方法进行重新渲染,所以在react中有一个名为shouldComponentUpdate
的钩子函数,它会根据你返回的布尔值来进行判断是否重新进行渲染,代码如下: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
26class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
上面的代码是官方给的例子,然而可以看出来如果state结构异常复杂的话(比如对象的嵌套
)这种比较是没有用的,这里存在着浅比较的问题这个钩子函数也会相应的变得复杂。(这里不明白的话可以去看shouldComponentUpdate
的源码,对于嵌套对象的比较是用的Object.is()
方法进行的比较)
对于这种情况,immutable就是一个非常好的解决方案,由于immutable的不可变性,我们可以非常轻松的在shouldComponentUpdate
中进行prevProps
和nextProps
的比较。
Immutable中提供is()方法来进行Immutable对象间的比较,它比较的是两个对象的 hashCode 或 valueOf,由于Immutable
内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。
Immutable与Redux摩擦出的火花
综上所述,为了避免浅比较存在的问题并且能够在redux数据流中进行react的渲染优化,我们可以将Immutable引入进来,引入Immutable就意味着要修改原本的数据结构类型,这就意味着我们要修改Action和Reducer以及组件中所有用到state的地方(工作量稍微有点庞大),对于Reducer
的修改 这里可以引入redux-immutable
这个库。
修改后的代码如下:
1 | //store.js |
1 | //reducers.js |
1 | //container.js |
对于container你也可以直接映射一个Immutable对象给state,通过get方法传递到元素中去,这里随便个人的喜好了。
结束
edit by AmamiRyoin