前言
一般來說在 JavaScript 中有兩種資料類型:Primitive(String、Number、Boolean、null、undefinded)和 Object(Reference)。在 JavaScript 中物件的操作比起 Java 容易很多,但也因?yàn)橄鄬椥圆粐?yán)謹(jǐn),所以產(chǎn)生了一些問題。在 JavaScript 中的 Object(物件)資料是 Mutable(可以變的),由于是使用 Reference 的方式,所以當(dāng)修改到複製的值也會修改到原始值。例如下面的 map2
值是指到 map1
,所以當(dāng) map1
值一改,map2
的值也會受影響。
var map1 = { a: 1 };
var map2 = map1;
map2.a = 2
通常一般作法是使用 deepCopy
來避免修改,但這樣作法會產(chǎn)生較多的資源浪費(fèi)。為了很好的解決這個問題,我們可以使用 Immutable Data
,所謂的 Immutable Data 就是一旦建立,就不能再被修改的數(shù)據(jù)資料。
為了解決這個問題,在 2013 年時 Facebook 工程師 Lee Byron 打造了 ImmutableJS,但并沒有被預(yù)設(shè)放到 React 工具包中(雖然有提供簡化的 Helper),但 ImmutableJS
的出現(xiàn)確實(shí)解決了 React
甚至 Redux
所遇到的一些問題。
以下范例即是引入了 ImmutableJS
的效果,讀者可以發(fā)現(xiàn),雖然我們操作了 map1
的值,但會發(fā)現(xiàn)原本的 map1
并未受到影響(因?yàn)槿魏涡薷亩疾粫绊懙皆假Y料),雖然使用 deepCopy
也可以模擬類似的效果但會浪費(fèi)過多的計(jì)算資源和記憶體,ImmutableJS
則可以容易地共享沒有被修該到的資料(例如下面的資料 b
即為 map1
所 map2
共享),因而有更好的效能表現(xiàn)。
import Immutable from 'immutable';
var map1 = Immutable.Map({ a: 1, b: 3 });
var map2 = map1.set('a', 2);
map1.get('a'); // 1
map2.get('a'); // 2
ImmutableJS 特性介紹
ImmutableJS 提供了 7 種不可修改的資料類型:List
、Map
、Stack
、OrderedMap
、Set
、OrderedSet
、Record
。若是對 Immutable 物件操作都會回傳一個新值。其中比較常用的有 List
、Map
和 Set
:
- Map:類似于 key/value 的 object,在 ES6 也有原生
Map
對應(yīng)
const Map= Immutable.Map;
// 1. Map 大小
const map1 = Map({ a: 1 });
map1.size
// => 1
// 2. 新增或取代 Map 元素
// set(key: K, value: V)
const map2 = map1.set('a', 7);
// => Map { "a": 7 }
// 3. 刪除元素
// delete(key: K)
const map3 = map1.delete('a');
// => Map {}
// 4. 清除 Map 內(nèi)容
const map4 = map1.clear();
// => Map {}
// 5. 更新 Map 元素
// update(updater: (value: Map<K, V>) => Map<K, V>)
// update(key: K, updater: (value: V) => V)
// update(key: K, notSetValue: V, updater: (value: V) => V)
const map5 = map1.update('a', () => (7))
// => Map { "a": 7 }
// 6. 合併 Map
const map6 = Map({ b: 3 });
map1.merge(map6);
// => Map { "a": 1, "b": 3 }
- List:有序且可以重複值,對應(yīng)于一般的 Array
const List= Immutable.List;
// 1. 取得 List 長度
const arr1 = List([1, 2, 3]);
arr1.size
// => 3
// 2. 新增或取代 List 元素內(nèi)容
// set(index: number, value: T)
// 將 index 位置的元素替換
const arr2 = arr1.set(-1, 7);
// => [1, 2, 7]
const arr3 = arr1.set(4, 0);
// => [1, 2, 3, undefined, 0]
// 3. 刪除 List 元素
// delete(index: number)
// 刪除 index 位置的元素
const arr4 = arr1.delete(1);
// => [1, 3]
// 4. 插入元素到 List
// insert(index: number, value: T)
// 在 index 位置插入 value
const arr5 = arr1.insert(1, 2);
// => [1, 2, 2, 3]
// 5. 清空 List
// clear()
const arr6 = arr1.clear();
// => []
- Set:沒有順序且不能重複的列表
const Set= Immutable.Set;
// 1. 建立 Set
const set1 = Set([1, 2, 3]);
// => Set { 1, 2, 3 }
// 2. 新增元素
const set2 = set1.add(1).add(5);
// => Set { 1, 2, 3, 5 }
// 由于 Set 為不能重複集合,故 1 只能出現(xiàn)一次
// 3. 刪除元素
const set3 = set1.delete(3);
// => Set { 1, 2 }
// 4. 取聯(lián)集
const set4 = Set([2, 3, 4, 5, 6]);
set1.union(set4);
// => Set { 1, 2, 3, 4, 5, 6 }
// 5. 取交集
set1.intersect(set4);
// => Set { 2, 3 }
// 6. 取差集
set1.subtract(set4);
// => Set { 1 }
ImmutableJS 的特性整理
- Persistent Data Structure
在ImmutableJS
的世界裡,只要資料一被創(chuàng)建,就不能修改,維持Immutable
。就不會發(fā)生下列的狀況:
var obj = {
a: 1
};
funcationA(obj);
console.log(obj.a) // 不確定結(jié)果為多少?
使用 ImmutableJS
就沒有這個問題:
// 有些開發(fā)者在使用時會在 ``Immutable` 變數(shù)前加 `$` 以示區(qū)隔。
const $obj = fromJS({
a: 1
});
funcationA($obj);
console.log($obj.get('a')) // 1
- Structural Sharing
為了維持資料的不可變,又要避免像deepCopy
一樣複製所有的節(jié)點(diǎn)資料而造成的資源損耗,在ImmutableJS
使用的是 Structural Sharing 特性,亦即如果物件樹中一個節(jié)點(diǎn)發(fā)生變化的話,只會修改這個節(jié)點(diǎn)和和受它影響的父節(jié)點(diǎn),其他節(jié)點(diǎn)則共享。
const obj = {
count: 1,
list: [1, 2, 3, 4, 5]
}
var map1 = Immutable.fromJS(obj);
var map2 = map1.set('count', 4);
console.log(map1.list === map2.list); // true
- Support Lazy Operation
Immutable.Range(1, Infinity)
.map(n => -n)
// Error: Cannot perform this action with an infinite size.
Immutable.Range(1, Infinity)
.map(n => -n)
.take(2)
.reduce((r, n) => r + n, 0);
// -3
豐富的 API 并提供快速轉(zhuǎn)換原生 JavaScript 的方式
在 ImmutableJS 中可以使用fromJS()
、toJS()
進(jìn)行 JavaScript 和 ImmutableJS 之間的轉(zhuǎn)換。但由于在轉(zhuǎn)換之間會非常耗費(fèi)資源,所以若是你決定引入ImmutableJS
的話請盡量維持資料處在Immutable
的狀態(tài)。支持 Functional Programming
Immutable
本身就是 Functional Programming(函數(shù)式程式設(shè)計(jì))的概念,所以在ImmutableJS
中可以使用許多 Functional Programming 的方法,例如:map
、filter
、groupBy
、reduce
、find
、findIndex
等。容易實(shí)現(xiàn) Redo/Undo 歷史回顧
React 效能優(yōu)化
ImmutableJS
除了可以和 Flux/Redux
整合外,也可以用于基本 react 效能優(yōu)化。以下是一般使用效能優(yōu)化的簡單方式:
傳統(tǒng) JavaScript 比較方式,若資料型態(tài)為 Primitive 就不會有問題:
// 在 shouldComponentUpdate 比較接下來的 props 是否一致,若相同則不重新渲染,提昇效能
shouldComponentUpdate (nextProps) {
return this.props.value !== nextProps.value;
}
但當(dāng)比較的是物件的話就會出現(xiàn)問題:
// 假設(shè) this.props.value 為 { foo: 'app' }
// 假設(shè) nextProps.value 為 { foo: 'app' },
// 雖然兩者值是一樣,但由于 reference 位置不同,所以視為不同。但由于值一樣應(yīng)該要避免重複渲染
this.props.value !== nextProps.value; // true
使用 ImmutableJS
:
var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'app' });
var y = x.set('foo', 'azz');
x === y; // false
在 ES6 中可以使用官方文件上的 PureRenderMixin
進(jìn)行比較,可以讓程式碼更簡潔:
import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}
總結(jié)
雖然 ImmutableJS
的引入可以帶來許多好處和效能的提升但由于引入整體檔案較大且較具侵入性,在引入之前可以自行評估看看是否合適于目前的專案。接下來我們將在后面的章節(jié)講解如何將 ImmutableJS
和 Redux
整合應(yīng)用到實(shí)務(wù)上的范例。
延伸閱讀
- 官方網(wǎng)站
- Immutable.js初識
- Immutable 詳解及 React 中實(shí)踐
- 為什么需要Immutable.js
- facebook immutable.js 意義何在,使用場景?
- React 巢狀 Component 效能優(yōu)化
- PureRenderMixin
- seamless-immutable
- Immutable Data Structures and JavaScript
(image via risingstack)