Immutable
1. Immutable Data
Immutable Data 就是一旦创建,就不能再更改的数据。对 Immutable 对象进行修改、添加或删除操作,都会返回一个新的 Immutable 对象。Immutable 实现的原理是持久化的数据结构(persistent data structure),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免深拷贝把所有节点都复制一遍带来的性能损耗,Immutable 使用了结构共享(structural sharing),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其他节点则进行共享。
3 种最重要的数据结构
- Map :键值对集合,对应于 Object ,ES6 也有专门的 Map 对象。
- List :有序可重复的列表,对应于 Array 。
- ArraySet :无序且不可重复的列表。
2. Immutable 的优点
- 降低了“可变”带来的复杂度
- 节省内存
- 撤销/重做,复制/粘贴,甚至时间旅行这些功能做起来都是小菜一碟
- 并发安全
- 拥抱函数式编程
3. Immutable 的缺点
- Immutable 中的 Map 和 List,取值时要用 map.get(‘key’) 而不是 map.key ,要用 array.get(0) 而不是 array[0]
- Immutable 每次修改都会返回新对象,很容易忘记赋值
- 容易忘记转换对象—>解决方案:
- 使用 FlowType 或 TypeScript 静态类型检查工具;
- 约定变量命名规则,如所有 Immutable 类型对象以 $$ 开头;
- 使用 Immutable.fromJS 而不是 Immutable.Map 或 Immutable.List 来创建对象,这样可以避免 Immutable 对象和原生对象间的混用。
4. Immutable.is
两个Immutable对象使用 === 来比较——直接比较内存地址,其性能最好
let map1 = Immutable.Map({a:1, b:1, c:1});
let map2 = Immutable.Map({a:1, b:1, c:1});
map1 === map2; // => false
即使两个对象的值是一样的,也会返回 false。用Immutable.is 来作“值比较”
Immutable.is(map1, map2); // => true
5. Immutable 与 cursor
import Immutable from 'immutable';
import Cursor from 'immutable/contrib/cursor';
let data = Immutable.fromJS({ a: { b: { c: 1 } } });
// 让 cursor 指向 { c: 1 }
let cursor = Cursor.from(data, ['a', 'b'], newData => {
// 当 cursor 或其子 cursor 执行更新时调用
console.log(newData);
});
cursor.get('c'); // 1
cursor = cursor.update('c', x => x + 1);
cursor.get('c'); // 2
6. Immutable 与 PureRender
shouldComponentUpdate | Immutable.js |
---|---|
React 做性能优化时最常用的就是 shouldComponentUpdate 方法,但它默认返回 true ,即始终会执行 render 方法,然后做 Virtual DOM 比较,并得出是否需要做真实 DOM的更新,这里往往会带来很多没必要的渲染 | Immutable.js 则提供了简洁、高效的判断数据是否变化的方法,只需 === 和 is 比较就能知道是否需要执行 render ,而这个操作几乎零成本,所以可以极大提高性能 |
import React, { Component } from 'react';
import { is } from 'immutable';
class Products extends Component {
shouldComponentUpdate(nextProps, nextState) {
const thisProps = this.props || {};
const 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 (nextProps.hasOwnProperty(key) &&
!is(thisProps[key], nextProps[key])) {
return true;
}
}
for (const key in nextState) {
if (nextState.hasOwnProperty(key) &&
!is(thisState[key], nextState[key])) {
return true;
}
}
return false;
}
}
export default Products;
7. Immutable 与 setState
React 建议把 this.state 当作不可变的,因此修改前需要做一个深拷贝
import React, { Component } from 'react';
import '_' from 'lodash';
class Products extends Component {
constructor(props) {
super(props);
this.state = {
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);
}
}
export default Products;
使用immutable
import React, { Component } from 'react';
class Products extends Component {
constructor(props) {
super(props);
this.state = {
data: Map({ times: 0 }),
}
}
handleAdd() {
this.setState(({ data }) => ({
data: data.update('times', v => v + 1),
}));
// 这时的 times 并不会改变
console.log(this.state.data.get('times'));
}
}
export default Products;
最好不要把 Immutable 对象直接暴露在对外的接口中