immutable-js、immer 以及其餘 N 個 immutable data 庫相關

前言

redux / flux 要求採用返回新對象的形式,來觸發數據更新、re-render,通常推薦的作法就是採用對象結構的方式:html

return {
  ...state,
  enabled: true,
}
複製代碼

若是要更改 state.settings.profile.darkmode,大概就會變成這樣:node

return {
  ...state,
  settings: {
    ...state.settings,
    profile:{
      ...state.settings.profile,
      darkmode: true,
    }
  }
}
複製代碼

以上存在兩個問題:react

  1. 若是 state 對象巨大(注意:對象巨大),在結構、拷貝 state 的過程當中,耗時會較長
  2. 如上更改 state.settings.profile.darkmode,要進行 「龐大」的工做

如何解決這兩個在使用 redux 過程當中可能存在的問題,即是此文討論的點。git

相應的此文,此文包含內容:github

先說結論,80% - 90% 的場景,直接使用 immer 便可。數據結構

不可變(Immutable)數據

facebook/immutable-js

倉庫地址:facebook/immutable-jsapp

參考閱讀:

簡言之:

  1. immutable-js 構建了一些新的數據結構,以空間換時間的方式,來解決 上述的 第一個 大結構對象 拷貝慢問題
  2. 並經過 stateMap.setIn(['settings', 'profile', 'darkmode'], true) 的方式,解決第二個問題

但相應的

  1. 若是對象不大,其實用不着 immutable-js
  2. 要留意 immutable 數據 和 原生的 數據之間的差別和操做

swannodette/mori

倉庫地址:swannodette/mori

應該是歷史遺留產物了,不贅述。

rtfeldman/seamless-immutable

倉庫地址:rtfeldman/seamless-immutable

var array = Immutable([1,2,3]);
array.map(value => [value+2, value+4]);
// returns Immutable([ [ 3, 5 ], [ 4, 6 ], [ 5, 7 ] ])

Immutable.flatMap(array, value => [value+2, value+4]);
// returns Immutable([ 3, 5, 4, 6, 5, 7 ])
複製代碼

參考閱讀:

凍結的不可變數組/對象,向後兼容 JS

相較 immutable-js,其沒有構建新的數據結構,而是在原有 JS array、object 上作了擴展,凍結了一些原生的 array、object 的方法,例如:poppush 等,在 dev 環境直接報錯

planttheidea/crio

倉庫地址:planttheidea/crio

具備 API 的不可變 JS 對象,與 seamless-immutable 其實大同小異,繼承了原生的 Array、Object,可是也 覆蓋 / 封裝 了 pushpop 等方法,提供了,使得其最終也會返回一個新的 crio immutable array

可是這其實和原生的 [].push[1, 2].pop 就會有差別,須要注意,原生這兩個方法返回的是 數組的長度,這引入的差別,感受更難以控制,反而弊大於利。

// you can assign with crio() directly
const crioArray = crio(['foo']);
const updatedCrioArray = crioArray.push('bar');

const crioObject = crio({foo: 'bar'});
const updatedCrioObject = crioObject.set('bar', 'baz');

// or use the convenience methods
const otherCrioArray = crio.array(['bar']);
const updatedOtherCrioArray = otherCrioArray.push('bar');

const otherCrioObject = crio.object({bar: 'baz'});
const updatedOtherCrioObject = otherCrioObject.set('bar', 'baz');
複製代碼

參考閱讀:

aearly/icepick

倉庫地址:aearly/icepick

又是一個輪子。和 seamless-immutable 幾乎相似。區別可能就在於 這個 是相似 lodash 同樣的,工具函數。而 seamless-immutable 則是面向對象的方式。

須要注意的是,其內部與 seamless-immutable 相似,是經過 object shallow copy、slice array 來建立新對象,而 planttheidea/crio 則是經過繼承、從新 new 的方式來作。

var coll = {a: 1, b: 2};
var newColl = icepick.assoc(coll, "b", 3); // {a: 1, b: 3}

var arr = ["a", "b", "c"];
var newArr = icepick.assoc(arr, 2, "d"); // ["a", "b", "d"]
複製代碼

參考閱讀:

綜合這 5 個庫,facebook/immutable-js 解決了最上方提到的兩個問題,可是相對來講比較重。

而 這三個,由於都是使用原生 JS 數據結構,相對的,其實解決的是上方的第二個問題,意義並非很大。

  • rtfeldman/seamless-immutable
  • planttheidea/crio
  • aearly/icepick

並且後面兩個倉庫 star 數量較少,固然具體代碼未細看,須要說具體問題、具體場景去具體分析,纔好作相應技術選型。

可是,若是第一個問題不突出,只是去解決 這第二個 「工做量大」 的問題,真的須要引入上述 immutable 相應的數據結構嗎?去記相應的新的對象、數組的新方法嗎?

不可變動新(Immutable Update)實用程序

此部分,就是單純處理第二個問題,沒有新的數據結構、數據對象。包含的四個工具庫:

直接推薦 壓軸的 mweststrate/immer

dot-prop-immutable

倉庫地址:debitoor/dot-prop-immutable

只是一些 helper 方法。

var dotProp = require('dot-prop-immutable');
var state = { todos: [] }, index = 0;

// Add todo:
state = dotProp.set(state, 'todos', list => [...list, {text: 'cleanup', complete: false}])
// or with destructuring assignment
state = {...state, todos: [...state.todos, {text: 'cleanup', complete: false}]};
//=> { todos: [{text: 'cleanup', complete: false}] }

// Complete todo:
state = dotProp.set(state, `todos.${index}.complete`, true)
// or with destructuring assignment
state = {...state, todos: [
	...state.todos.slice(0, index),
	{...state.todos[index], complete: true},
	...state.todos.slice(index + 1)
]};
//=> { todos: [{text: 'cleanup', complete: true}] }

// Delete todo:
state = dotProp.delete(state, `todos.${index}`)
// or with destructuring assignment
state = {...state, todos: [
	...state.todos.slice(0, index),
	...state.todos.slice(index + 1)
]};
//=> { todos: [] }
複製代碼

kolodny/immutability-helper

倉庫地址:kolodny/immutability-helper

只是一些 helper 寫法 (以 $method 方式)

import update from 'immutability-helper';

const newData = update(myData, {
  x: {y: {z: {$set: 7}}},
  a: {b: {$push: [9]}}
});

const initialArray = [1, 2, 3];
const newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]

const collection = [1, 2, {a: [12, 17, 15]}];
const newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
// => [1, 2, {a: [12, 13, 14, 15]}]


const obj = {a: 5, b: 3};
const newObj = update(obj, {b: {$apply: function(x) {return x * 2;}}});
// => {a: 5, b: 6}
// This is equivalent, but gets verbose for deeply nested collections:
const newObj2 = update(obj, {b: {$set: obj.b * 2}});
複製代碼

mariocasciaro/object-path-immutable

倉庫地址:mariocasciaro/object-path-immutable

大同小異,helper 方法,返回新的數據對象

const newObj1 = immutable.set(obj, 'a.b', 'f')
const newObj2 = immutable.set(obj, ['a', 'b'], 'f')

// {
// a: {
// b: 'f',
// c: ['d', 'f']
// }
// }

// Note that if the path is specified as a string, numbers are automatically interpreted as array indexes.

const newObj = immutable.set(obj, 'a.c.1', 'fooo')
// {
// a: {
// b: 'f',
// c: ['d', 'fooo']
// }
// }
複製代碼

mweststrate/immer

倉庫地址:mweststrate/immer

import produce from "immer"

const baseState = [
    {
        todo: "Learn typescript",
        done: true
    },
    {
        todo: "Try immer",
        done: false
    }
]

const nextState = produce(baseState, draftState => {
    draftState.push({todo: "Tweet about it"})
    draftState[1].done = true
})
複製代碼

和上面的思路不同,原始對象先作了一層 Proxy 代理,獲得 draftState 傳遞給 function。

function(帶反作用) 直接更改 draftState,最後 produce 返回新的對象。

推薦直接使用 immer,畢竟人是 mobx 的做者,畢竟是獲了獎的。並且其寫法特別符合人的直覺,還省了 returnproduce 函數內部幫咱們作掉了)

參考閱讀:精讀《Immer.js》源碼

Immutable/Redux 互操做

gajus/redux-immutable

倉庫地址:gajus/redux-immutable

將 immutable-js 與 redux 結合的工具

eadmundo/redux-seamless-immutable

倉庫地址:eadmundo/redux-seamless-immutable

將 seamless-immutable 與 redux 結合的工具

既然,如無必要不會使用 immutable-js 去處理第一個問題,那麼,第二個問題 使用 immer 能夠作的很好,也就不必使用這兩個工具了。

其餘參考

redux - 生態系統 - 不可變(Immutable)數據

相關文章
相關標籤/搜索