React杂谈之Immutable

React杂谈之Immutable.js

Immutable.js是什么,有何意义

immutable从英文翻译过来叫做不可改变的,所以Immutable.js就是用来生成不可变的数据的。

Javascript的缺陷

众所周知在javascript中,对象一般是可变的,如下:

1
2
3
4
let 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
4
import 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
    5
    function 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的好处

其实下面这张图更方便理解:

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
26
class 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中进行prevPropsnextProps的比较。

Immutable中提供is()方法来进行Immutable对象间的比较,它比较的是两个对象的 hashCode 或 valueOf,由于Immutable内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。

Immutable与Redux摩擦出的火花

综上所述,为了避免浅比较存在的问题并且能够在redux数据流中进行react的渲染优化,我们可以将Immutable引入进来,引入Immutable就意味着要修改原本的数据结构类型,这就意味着我们要修改Action和Reducer以及组件中所有用到state的地方(工作量稍微有点庞大),对于Reducer的修改 这里可以引入redux-immutable这个库。

修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//store.js

import { createStore } from 'redux';
import { combineReducers } from 'redux-immutablejs';

import Immutable from 'immutable';
import * as reducers from './reducers';

const reducer = combineReducers(reducers);
const state = Immutable.fromJS({});

const store = reducer(state);
export default createStore(reducer, store);
`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//reducers.js

import { createReducer } from 'redux-immutablejs'
const initialState = Immutable.fromJS({ isAuth: false })

/**
* Reducer domain that handles authentication & authorization.
**/
export default createReducer(initialState, {
[LOGIN]: (state, action) => state.merge({
isAuth: true,
token: action.payload.token
})
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//container.js

const mapStateToProps = state => ({
todos: state.get('todos').get('items')
})

const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(TodoActions, dispatch)
})

export default connect(
mapStateToProps,
mapDispatchToProps
)(App)

对于container你也可以直接映射一个Immutable对象给state,通过get方法传递到元素中去,这里随便个人的喜好了。

结束

edit by AmamiRyoin

觉得不错的话可以打赏哦