Immutable.js

1. 來源

Immutable.js出自Facebook砚蓬,是最流行的不可變數(shù)據結構的實現(xiàn)之一餐屎。它實現(xiàn)了完全的持久化數(shù)據結構析命,通過使用像tries這樣的先進技術來實現(xiàn)結構共享。所有的更新操作都會返回新的值局骤,但是在內部結構是共享的,來減少內存占用(和垃圾回收的失效)暴凑。

2. immutable.js三大特性:

  • Persistent data structure (持久化數(shù)據結構)
  • structural sharing (結構共享)
  • support lazy operation (惰性操作)
2.1. 持久化數(shù)據結構

這里說的持久化是用來描述一種數(shù)據結構峦甩,指一個數(shù)據,在被修改時现喳,仍然能夠保持修改前的狀態(tài)凯傲,即不可變類型。immutable.js提供了十余種不可變的類型(List嗦篱,Map冰单,Set,Seq灸促,Collection诫欠,Range等)

2.2. 結構共享

Immutable使用先進的tries(字典樹)技術實現(xiàn)結構共享來解決性能問題,當我們對一個Immutable對象進行操作的時候腿宰,ImmutableJS會只clone該節(jié)點以及它的祖先節(jié)點呕诉,其他保持不變缘厢,這樣可以共享相同的部分吃度,大大提高性能。

圖片來自網絡
2.3. 惰性操作
const oddSquares = Immutable.seq.of(1, 2, 3, 4, 5, 6, 7, 8)
  .filter(item => {
    console.log('immutable對象的filter執(zhí)行');
    return item % 2;
  })
  .map(x => x * x);

console.log(oddSquares.get(1)); // 9

const jsSquares = [1, 2, 3, 4, 5, 6, 7, 8]
  .filter(item => {
    console.log('原生數(shù)組的filter執(zhí)行');
    return item % 2;
  })
  .map(x => x * x);

console.log(jsSquares[1]); // 9
執(zhí)行結果

用seq創(chuàng)建的對象贴硫,其實代碼塊沒有被執(zhí)行椿每,只是被聲明了,代碼在get(1)的時候才會實際被執(zhí)行英遭,取到index=1的數(shù)之后间护,后面的就不會再執(zhí)行了,所以在filter時挖诸,第三次就取到了要的數(shù)汁尺,從4-8都不會再執(zhí)行。如果在實際業(yè)務中多律,數(shù)據量非常大痴突,一個array的長度是幾百,要操作這樣一個array狼荞,如果應用惰性操作的特性辽装,會節(jié)省非常多的性能。

3. 常用API介紹

// Map(): 原生object轉Map對象 (只會轉換第一層相味,注意和fromJS區(qū)別)
immutable.Map({ name: 'graceji', age: 18 });

// List(): 原生array轉List對象 (只會轉換第一層拾积,注意和fromJS區(qū)別)
immutable.List([1, 2, 3, 4, 5]);

// fromJS(): 原生js轉immutable對象  (深度轉換,會將內部嵌套的對象和數(shù)組全部轉成immutable)
immutable.fromJS([1, 2, 3, 4, 5]);    // 原生array  --> List
immutable.fromJS({ name: 'graceji', age: 18 });   // 原生object  --> Map

// toJS(): immutable對象轉原生js  (深度轉換,會將內部嵌套的Map和List全部轉換成原生js)
immutableData.toJS();

// 查看List或者map大小  
immutableData.size  或者 immutableData.count()

// is(): 判斷兩個immutable對象是否相等
const objA = { name: 'graceji', age: 18 };
const objB = { name: 'graceji', age: 18 };
const imA = immutable.Map({ name: 'graceji', age: 18 });
const imB =immutable.Map({ name: 'graceji', age: 18 });
objsA === objB // false拓巧; 比較的是地址
immutable.is(imA, imB); // true; hashcode相同

// merge()  對象合并
const imA = immutable.fromJS({ a: 1,b: 2 });
const imA = immutable.fromJS({ c: 3 });
const imC = imA.merge(imB);
console.log(imC.toJS())  // { a: 1,b: 2,c: 3 }

// 增刪改查(所有操作都會返回新的值斯碌,不會修改原來值)
const immutableData = immutable.fromJS({
    a: 1,
    b: 2,
    c: {
        d: 3
    }
});
const data1 = immutableData.get('a') //  data1 = 1  
const data2 = immutableData.getIn(['c', 'd']) // data2 = 3; getIn用于深層結構訪問
const data3 = immutableData.set('a' , 2);   // data3中的 a = 2
const data4 = immutableData.setIn(['c',  'd'],  4);   // data4中的 d = 4
const data5 = immutableData.update('a' , function(x) { return x+4 })   // data5中的 a = 5
const data6 = immutableData.updateIn(['c', 'd'], function(x) { return x+4 })   // data6中的 d = 7
const data7 = immutableData.delete('a')   // data7中的 a 不存在
const data8 = immutableData.deleteIn(['c',  'd'])   // data8中的 d 不存在

4. 優(yōu)缺點

優(yōu)點:

  • 降低mutable帶來的復雜度
  • 節(jié)省內存
  • 歷史追溯性(時間旅行)

時間旅行指的是肛度,每時每刻的值都被保留了输拇,想回退到哪一步只要簡單的將數(shù)據取出就行。如果現(xiàn)在頁面有個撤銷的操作贤斜,撤銷前的數(shù)據被保留了策吠,只需要取出就行,這個特性在redux或者flux中特別有用

  • 擁抱函數(shù)式編程:immutable本來就是函數(shù)式編程的概念瘩绒,純函數(shù)式編程的特點就是猴抹,只要輸入一致,輸出必然一致锁荔,相比于面向對象蟀给,這樣開發(fā)組件和調試更方便

缺點:

  • 需要重新學習api
  • 資源包大小增加(源碼5000行左右)
  • 容易與原生對象混淆:由于api與原生不同,混用的話容易出錯阳堕。

5. 在react+redux中集成immutable.js

react有個重要的性能優(yōu)化的點就是shouldComponentUpdate跋理,返回true代碼該組件要re-render,false則不重新渲染恬总。簡單的場景可以直接使用===去判斷this.props和nextProps是否相等前普,但當props是一個復雜的結構時,===肯定是沒用的壹堰。

5.1 集成前的準備

首先需要確定哪些數(shù)據需要使用不可變數(shù)據拭卿,哪些數(shù)據要使用原生js數(shù)據結構,哪些地方需要做互相轉換贱纠。

  • 在redux中峻厚,全局state必須是immutable的,這是使用immutable來優(yōu)化redux的核心
  • 組件props是通過redux的connect從state中獲得的谆焊,并且引入immutablejs的另一個目的是減少組件shouldComponentUpdate中不必要渲染惠桃,shouldComponentUpdate中比對的是props,如果props是原生js就失去了優(yōu)化的意義
  • 組件內部state如果需要提交到store的辖试,必須是immutable辜王,否則不強制
  • view提交到action中的數(shù)據必須是immutable
  • action提交到reducer中的數(shù)據必須是immutable
  • reducer中最終處理state必須是以immutable的形式處理并返回
  • 與服務端ajax交互的數(shù)據為原生js數(shù)據,需要轉換成immutable數(shù)據
    從上面這些點可以看出剃执,幾乎整個項目都是必須使用immutable的誓禁,只有在少數(shù)與外部依賴有交互的地方使用了原生js。這么做的目的其實就是為了防止在大型項目中肾档,原生js與immutable混用摹恰,導致自己都不清楚一個變量中存儲的到底是什么類型的數(shù)據辫继。
    Note:fromJS() 和 toJS() 是深層的互轉immutable對象和原生對象,性能開銷大俗慈,盡量不要使用姑宽。
5.2 具體實現(xiàn)
  • redux-immutable
    redux中利用combineReducers來合并reducer并初始化state,redux自帶的combineReducers只支持state是原生js形式的闺阱,所以需要使用redux-immutable提供的combineReducers來替換原來的方法炮车。
import { combineReducers } from 'redux-immutable';
import reducer1 from './reducer1';
import reducer2 from './reducer2';
import reducer3 from './reducer3';

const rootReducer = combineReducers({
    reducer1,
    reducer2,
    reducer3,
});

export default rootReducer;
  • reducer中的initialState也需要初始化成immutable類型:
const initialState = Immutable.Map({});
const reducer1 = (state = initialState, action) => {
    switch (action.type) {
    case ILOVEYOU:
        return state.set('smile', true);
    }
}
export default reducer1;
  • state成為了immutable類型, 所以通過mapStateToProps傳入組件的屬性寫法也需要做相應改變
// connect
function mapStateToProps (state) {
    return {
        lovers: state.getIn(['man', 'list']),  
        abhorrers: state.getIn(['women', 'list']) // 使用get或者getIn來獲取state中的變量
    }
}
  • 服務端交互ajax
// 偽代碼
$.ajax({
    type: 'GET',
    url: 'XXX',
    dataType: 'json',
    success(res) {
        res = immutable.fromJS(res || {});
        callback && callback(res);
    },
    error(e) {
        e = immutable.fromJS(e || {});
        callback && callback(e);
    },
});
  • shouldComponentUpdate
// BaseComponent.js 基類

import React, { Component } from 'react';
import { is } from 'immutable';

class BaseComponent extends Component {
    constructor(props, context) {
        super(props, context);
    }

    shouldComponentUpdate (nextProps, nextState) {
        const thisProps = this.props || {};
        const thisState = this.state || {};
        nextState = nextState || {};
        nextProps = nextProps || {};

        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 (!is(thisProps[key], nextProps[key])) {
                return true;
            }
        }

        for (const key in nextState) {
            if (!is(thisState[key], nextState[key])) {
                return true;
            }
        }
        return false;
    }
}

export default BaseComponent;

組件中如果需要使用統(tǒng)一封裝的shouldComponentUpdate,則直接繼承基類, 如果不想組件使用封裝的方法酣溃,那直接在該組件中重寫shouldComponentUpdate就行了瘦穆。

import BaseComponent from './BaseComponent'; 
class People extends BaseComponent {
    constructor() {
        super();
    }
    …………
}

6. immutable.js使用過程中的一些注意點

  • fromJS和toJS會深度轉換數(shù)據,隨之帶來的開銷較大赊豌,盡可能避免使用扛或,單層數(shù)據轉換使用Map()和List()
  • Map類型的key必須是string
  • 所有針對immutable變量的增刪改必須左邊有賦值,因為所有操作都不會改變原來的值碘饼,只是生成一個新的變量
// js
const arr = [1,2,3,4];
arr.push(5);
console.log(arr); // [1,2,3,4,5]
//immutable
const arr = immutable.fromJS([1,2,3,4])
// 錯誤用法
arr.push(5);
console.log(arr) //[1,2,3,4]
// 正確用法
arr = arr.push(5);
console.log(arr) // [1,2,3,4,5]
  • 引入immutablejs后熙兔,不應該再出現(xiàn)對象數(shù)組拷貝的代碼
// es6對象復制
const state = Object.assign({}, state, {
    key: value
});

// array復制
const newArr = [].concat([1, 2, 3]);
  • immutable對象直接可以轉JSON.stringify(),不需要顯式手動調用toJS()轉原生
  • 判斷對象是否是空可以直接用size
  • 調試過程中要看一個immutable變量中真實的值,可以chrome中加斷點艾恼,在console中使用.toJS()方法來查看

7. 總結

總的來說immutable.js完美的契合了react+redux的state流處理住涉,redux的宗旨就是單一數(shù)據流,可追溯钠绍,這兩點恰恰是immutable.js的優(yōu)勢舆声。當然也不是所有使用react+redux的場景都需要使用immutable.js,建議滿足項目足夠大五慈,state結構足夠復雜的原則纳寂,小項目可以手動處理shouldComponentUpdate,不建議使用泻拦,得不償失。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末忽媒,一起剝皮案震驚了整個濱河市争拐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晦雨,老刑警劉巖架曹,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異闹瞧,居然都是意外死亡绑雄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門奥邮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來万牺,“玉大人罗珍,你說我怎么就攤上這事〗潘冢” “怎么了覆旱?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長核无。 經常有香客問我扣唱,道長,這世上最難降的妖魔是什么团南? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任噪沙,我火速辦了婚禮,結果婚禮上吐根,老公的妹妹穿的比我還像新娘曲聂。我一直安慰自己,他們只是感情好佑惠,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布朋腋。 她就那樣靜靜地躺著,像睡著了一般膜楷。 火紅的嫁衣襯著肌膚如雪旭咽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天赌厅,我揣著相機與錄音穷绵,去河邊找鬼。 笑死特愿,一個胖子當著我的面吹牛仲墨,可吹牛的內容都是我干的。 我是一名探鬼主播揍障,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼目养,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了毒嫡?” 一聲冷哼從身側響起癌蚁,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兜畸,沒想到半個月后努释,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡咬摇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年伐蒂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肛鹏。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡逸邦,死狀恐怖恩沛,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情昭雌,我是刑警寧澤复唤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站烛卧,受9級特大地震影響佛纫,放射性物質發(fā)生泄漏。R本人自食惡果不足惜总放,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一呈宇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧局雄,春花似錦甥啄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宫盔,卻和暖如春融虽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灼芭。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工有额, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彼绷。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓巍佑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寄悯。 傳聞我的和親對象是個殘疾皇子萤衰,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內容