Professional Documents
Culture Documents
Immutable详解及React中实践
Immutable详解及React中实践
登录
camsong ·
1 年前
-- Pete Hunt
JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原
始对象。如 `foo={a: 1}; bar=foo; bar.a=2` 你会发现此时 `foo.a` 也被改成了 `2`。虽然这样做可以节约内存,但当应用复杂后,这就
造成了非常大的隐患,Mutable 带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或
deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。
Immutable 可以很好地解决这些问题。
1. immutable.js
Facebook 工程师 Lee Byron 花费 3 年时间打造,与 React 同期出现,但没有被默认放到 React 工具集里(React 提供了简化的
Helper)。它内部实现了一套完整的 Persistent Data Structure,还有很多易用的数据类型。像 `Collection`、`List`、`Map`、`Set`、
`Record`、`Seq`。有非常全面的`map`、`filter`、`groupBy`、`reduce``find`函数式操作方法。同时 API 也尽量与 Object 或 Array 类
似。
List:有序可重复的列表,对应于 Array
Set:无序且不可重复的列表
2. seamless-immutable
下面上代码来感受一下两者的不同:
// 原来的写法
let foo = {a: {b: 1}};
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b); // 打印 2
console.log(foo === bar); // 打印 true
// 使用 immutable.js 后
import Immutable from 'immutable';
foo = Immutable.fromJS({a: {b: 1}});
bar = foo.setIn(['a', 'b'], 2); // 使用 setIn 赋值
console.log(foo.getIn(['a', 'b'])); // 使用 getIn 取值,打印 1
console.log(foo === bar); // 打印 false
// 使用 seamless-immutable.js 后
import SImmutable from 'seamless-immutable';
foo = SImmutable({a: {b: 1}})
bar = foo.merge({a: { b: 2}}) // 使用 merge 赋值
console.log(foo.a.b); // 像原生 Object 一样取值,打印 1
Immutable 优点
比如下面一段代码:
function touchAndLog(touchFn) {
let data = { key: 'value' };
touchFn(data);
console.log(data.key); // 猜猜会打印什么?
}
2. 节省内存
a === b; // false
3. Undo/Redo,Copy/Paste,甚至时间旅行这些功能做起来小菜一碟
因为每次数据都是不一样的,只要把这些数据放到一个数组里储存起来,想回退到哪里就拿出对应数据即可,很容易开发出撤销重
做这种功能。
4. 并发安全
5. 拥抱函数式编程
Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样
开发的组件更易于调试和组装。
使用 Immutable 的缺点
1. 需要学习新的 API
No Comments
2. 增加了资源文件大小
No Comments
3. 容易与原生对象混淆
当使用外部库的时候,一般需要使用原生对象,也很容易忘记转换。
下面给出一些办法来避免类似问题发生:
更多认识
1. Immutable.is
`Immutable.is` 比较的是两个对象的 `hashCode` 或 `valueOf`(对于 JavaScript 对象)。由于 immutable 内部使用了 Trie 数据结构来
存储,只要两个对象的 `hashCode` 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。
2. 与 Object.freeze、const 区别
3. Cursor 的概念
这个 Cursor 和数据库中的游标是完全不同的概念。
cursor.get('c'); // 1
cursor = cursor.update('c', x => x + 1);
cursor.get('c'); // 2
实践
setState 的一个技巧
使用 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() {
this.setState(({data}) => ({
data: data.update('times', v => v + 1) })
});
}
2. 与 Flux 搭配使用
现在是实现一个类似带有添加和撤销功能的 Store:
Dispatcher.register(action => {
if (action.actionType === 'create') {
let id = createGUID();
history.push(todos); // 记录当前操作前的数据,便于撤销
todos = todos.set(id, Map({
id: id,
complete: false,
text: action.text.trim()
}));
TodoStore.emitChange();
} else if (action.actionType === 'undo') {
// 这里是撤销功能实现,
// 只需从 history 数组中取前一次 todos 即可
if (history.length > 0) {
todos = history.pop();
}
TodoStore.emitChange();
}
});
3. 与 Redux 搭配使用
Redux 是目前流行的 Flux 衍生库。它简化了 Flux 中多个 Store 的概念,只有一个 Store,数据操作通过 Reducer 中实现;同时它提
供更简洁和清晰的单向数据流(View -> Action -> Middleware -> Reducer),也更易于开发同构应用。目前已经在我们项目中大规
模使用。
由于 Redux 中内置的 `combineReducers` 和 reducer 中的 `initialState` 都为原生的 Object 对象,所以不能和 Immutable 原生搭配使
用。
上面我们提到 Cursor 可以方便检索和 update 层级比较深的数据,但因为 Redux 中已经有了 select 来做检索,Action 来更新数据,
因此 Cursor 在这里就没有用武之地了。
总结
Immutable 可以给应用带来极大的性能提升,但是否使用还要看项目情况。由于侵入性较强,新项目引入比较容易,老项目迁移需
要评估迁移。对于一些提供给外部使用的公共组件,最好不要把 Immutable 对象直接暴露在对外接口中。
> 码这么多字不容易,喜欢就给个赞吧,亲
「如果这篇文章帮助到你,就请我喝杯咖啡吧~」
赞赏
2 人赞赏
React
Flux
JavaScript
363
分享
举报
文章被以下专栏收录
pure render
分享关于 React 相关经验及发展动态
进入专栏
49 条评论
写下你的评论
下一页
推荐阅读
序 · 为 React 而写
pure render
中超 U23妖星介绍: 17岁试训利物浦,曾获最佳新人提名,国青主力中卫新赛季蓄势待发
编辑精选