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
state
對象巨大(注意:對象巨大),在結構、拷貝 state 的過程當中,耗時會較長state.settings.profile.darkmode
,要進行 「龐大」的工做如何解決這兩個在使用 redux 過程當中可能存在的問題,即是此文討論的點。git
相應的此文,此文包含內容:github
不可變(Immutable)數據typescript
不可變動新(Immutable Update)實用程序redux
Immutable/Redux 互操做數組
先說結論,80% - 90% 的場景,直接使用 immer 便可。數據結構
倉庫地址:facebook/immutable-jsapp
參考閱讀:
簡言之:
stateMap.setIn(['settings', 'profile', 'darkmode'], true)
的方式,解決第二個問題但相應的
倉庫地址:swannodette/mori
應該是歷史遺留產物了,不贅述。
倉庫地址: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 的方法,例如:pop
、push
等,在 dev 環境直接報錯
倉庫地址:planttheidea/crio
具備 API 的不可變 JS 對象,與 seamless-immutable 其實大同小異,繼承了原生的 Array、Object,可是也 覆蓋 / 封裝 了 push
、pop
等方法,提供了,使得其最終也會返回一個新的 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
又是一個輪子。和 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 數據結構,相對的,其實解決的是上方的第二個問題,意義並非很大。
並且後面兩個倉庫 star 數量較少,固然具體代碼未細看,須要說具體問題、具體場景去具體分析,纔好作相應技術選型。
可是,若是第一個問題不突出,只是去解決 這第二個 「工做量大」 的問題,真的須要引入上述 immutable 相應的數據結構嗎?去記相應的新的對象、數組的新方法嗎?
此部分,就是單純處理第二個問題,沒有新的數據結構、數據對象。包含的四個工具庫:
直接推薦 壓軸的 mweststrate/immer
倉庫地址: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
只是一些 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
大同小異,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
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 的做者,畢竟是獲了獎的。並且其寫法特別符合人的直覺,還省了 return
(produce
函數內部幫咱們作掉了)
參考閱讀:精讀《Immer.js》源碼
將 immutable-js 與 redux 結合的工具
倉庫地址:eadmundo/redux-seamless-immutable
將 seamless-immutable 與 redux 結合的工具
既然,如無必要不會使用 immutable-js 去處理第一個問題,那麼,第二個問題 使用 immer 能夠作的很好,也就不必使用這兩個工具了。