在講不可變資料(Immutable Data)前,先說說可變資料(Mutable Data),在原生js中建立的資料都是可變的,如:
var a = {qty:
1}
a.qty =
10;
可能有小夥伴說,可以用const啊,const對基本資料型別還行,但對引用資料型別根本沒轍,如
const a = {qty:
1}
a.qty =
10;
a.qty;
// 10
如果把物件a賦值給其它變數還會導致新的問題,如:
const a = {qty:
1}
const b = a;
a.qty =
10;
b.qty;
//10
這時你會發現,修改了a,b的值也跟著改了,這其實是js採用引用賦值的方式來實現資料共享的,好處就是節省記憶體,但缺點也顯而易見,稍微不注意就會導致改A壞B的棘手問題,在複雜的專案中,這種問題還不易排查,有諸多安全隱患。
之前的做法是,利用深複製的方式來解決這個問題,雖然問題解決了,但又會引發新的問題:浪費記憶體,還有對一些需要頻繁更新資料又有高效能要求的場景(如:React),深複製實則為一個不明智的操作,於是,Imutable.js的出現就是要解決這些開發痛點的。
Immutable.js 由Facebook 工程師 Lee Byron 花費 3 年時間打造,在js中的引用賦值可以節省記憶體,但隨著應用的不斷複雜後,狀態的改變往往會變成噩夢,通常的做法是 複製資料來避免被修改,但這樣又造成了CPU和記憶體的消耗,而Immutable利用 結構共享可以很好地解決這些問題。
不可變資料:Immutable Data
Immutable Data 是一旦建立,就不能再被更改的資料。對 Immutable 物件的任何修改或新增刪除操作都會返回一個新的 Immutable 物件。Immutable 實現的原理是 Persistent Data Structure(持久化資料結構),也就是對於不需要改變的資料採用結構共享的方式,如下圖
<!-- ![持久化資料結構](./img/immutable.js結構共享.webp "持久化資料結構") -->
常用資料型別
- List: 有序索引集,類似JS中的Array。
- Map: 無序索引集,類似JS中的Object。
- OrderedMap: 有序的Map,根據資料的set()進行排序。
- Set: 沒有重複值的集合。
- OrderedSet: 有序的Set,根據資料的add進行排序。
- Stack: 有序集合,支援使用unshift()和shift()新增和刪除。
- Record: 一個用於生成Record例項的類。類似於JavaScript的Object,但是隻接收特定字串為key,具有預設值。
- Seq: 序列,但是可能不能由具體的資料結構支援。
- Collection: 是構建所有資料結構的基類,不可以直接構建。
正如你看到的,immutable.js的資料型別有很多,本文主要介紹比較常用的List和Map,對應於js中的陣列和物件。
js與immutable之間的轉換
可透過fromJS()和toJS()兩個方式實現js和immuatble資料的轉換,如:
import Immutable
from
'immutable';
const goods = {
name:
'huawei mate30 pro',
price:
5998,
brand:
'huawei'}
// js -> immutable data
const imData = Immutable.fromJS(goods)
// immutable data -> js
imData.toJS()
但fromJS()和toJS()會深度轉換資料,隨之帶來的開銷較大,儘可能避免使用,單層資料轉換應直接使用Map()和List()進行轉換。另外,還可以直接透過JSON.stringify()對immutable資料轉換也json字串。
import {
Map,List}
from
'immutable';
const initState =
Map({
breadcrumb:List([]),
user:
Map({}),
manageMenuStatus:
false
})
操作immutable資料
獲取immutable中的值:get(key)/getIn(keys)
Map 和 List的通用方法,實現如下
import {Map,List}
from
'immutable';
let state = Map({
version:
'2.0',
user:Map({
username:
'laoxie',
age:
18,
hobby:List([
'程式碼',
'電影',
'唱歌'])
}),
})
// 獲取 version
state.get(
'version');
//
2.0
// 獲取username
state.getIn([
'user',
'username']);
// laoxie
// 獲取hobby屬性資料
state.getIn([
'user',
'hobby',
1])
// 電影
注意: 和傳統的js不同,getIn()獲取深層深套物件的值時不需要做每一層級的判斷是否存在,如不存在則會返回undefined(JS中如果不判空會報錯)
- 新增immutable中的資料:set(key,val)/setIn(keys,val)
- 刪除屬性:delete(key)/deleteIn(keys)
- 更新屬性:update(key,val=>newVal)/updateIn(keys,val=>newVal) 如開頭所說的,Immutable Data為不可變資料,所有針對immutable的增刪改都不會修改原資料,而是返回一個新的值,所以需要給變數重新賦值。
import {Map,List}
from
'immutable';
let state = Map({
version:
'2.0',
user:Map({
id:
'123',
username:
'laoxie',
age:
18,
hobby:List([
'程式碼',
'電影',
'唱歌'])
}),
})
state.set(
'version',
'3.0');
state.get(
'version');
//state不被修改,所以還是返回
2.0
// 正確的修改方式:修改後重新賦值
state = state.setIn([
'user',
'age'],
20);
state.getIn([
'user',
'age']);
//
20
// update,
delete操作同上
- 判斷是否存在某個屬性:has(key)/hasIn(keys) 這應該也是實際開發中是比較常用的方法,透過判斷屬性是否存在來執行不同的操作,如可以判斷user.id來判斷使用者是否登入
if(state.hasIn([
'user',
'id'])){
// 使用者已經登入
}
else{
// 使用者未登入
}
- 判斷兩個資料是否相等: is(imA,imB) 在JS中,不管是資料還是物件,透過==或===只能判斷兩個變數的引用地址是否為同一個物件,很難判斷兩個物件的鍵值是否相等,與JS不同,immutable是對兩個物件的hashCode和valueOf進行比較的
- 資料合併:merge()/mergeDeep() 還有一個比較常用的操作就是合併資料了,在JS我們一般使用Object.assign()來實現,但Object.assign()只能做淺合併,對層級較深的資料可以使用immutable中使用mergeDeep()來實現,兩個方法都返回合併後的資料。
const imA = Map({
username:
'馬雲',
money:
150000000000,
info:{
married:
true,
witticism:
'我沒見過錢,我對錢不感興趣'
}
})
const imB = Map({
username:
'laoxie',
gender:
'男',
info:{
married:
false,
age:
18,
}
})
const newImData = imA.merge(imB);
console.log(newImData.toJS());
//輸出 :
/
/ {
/
/ username:'laoxie',
/
/ gender:'男',
/
/ money:150000000000,
/
/ info:{
/
/ married:false,
/
/ age:18,
/
/ }
/
/ }
const newImData = imA.mergeDeep(imB);
/
/輸出 :
/
/ {
/
/ username:'laoxie',
/
/ gender:'男',
/
/ money:150000000000,
/
/ info:{
/
/ married:false,
/
/ age:18,
/
/ witticism:'我沒見過錢,我對錢不感興趣'
/
/ }
/
/ }
當然Immutable的方法還有很多,本文字只涉及到一引起基本操作,如果想要了解跟多資料型別的操作,請自行檢視官網