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 能够作的很好,也就不必使用这两个工具了。