不可變資料之Immutable

千鋒Python唐小強發表於2020-08-24

在講不可變資料(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 "持久化資料結構") -->

不可變資料之Immutable

常用資料型別

  • 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的方法還有很多,本文字只涉及到一引起基本操作,如果想要了解跟多資料型別的操作,請自行檢視官網


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2714021/,如需轉載,請註明出處,否則將追究法律責任。

相關文章