传播运算符与 immutable.js
Posted
技术标签:
【中文标题】传播运算符与 immutable.js【英文标题】:spread operator vs immutable.js 【发布时间】:2019-04-25 13:11:27 【问题描述】:似乎在将 redux 与 react 结合使用时,immutable.js 几乎已成为行业标准。 我的问题是,当我们使用扩展运算符时,我们不是对我们的 redux 状态进行不变的更改吗?例如,
const reducer = (state=initialState, action) =>
switch(action.type)
case actionType.SOME_ACTION:
return
...state,
someState: state.someState.filter(etc=>etc)
我用 redux 设置状态的方式不是不可变的吗?使用 immutable.js OVER 扩展运算符使对象不可变有什么好处?
抱歉,如果有人问过这个问题,但我找不到令我满意的答案。我了解不可变对象的好处,但不了解使用 immutable.js 库而不是点运算符的重要性。
【问题讨论】:
【参考方案1】:简答
是的! ES6 扩展运算符可以完全替代 immutable.js,但有一个主要警告,您必须始终保持态势感知。
很长的答案
您和其他开发人员将 100% 负责维护不变性,而不是让 immutable.js 为您处理。下面详细介绍了如何使用 ES6 的“扩展运算符”以及它的各种函数(如 filter
和 map
)自行管理不可变状态。
下面将探讨以不可变和变异的方式向数组或对象中删除和添加值。我在每个示例中注销了initialState
和newState
,以证明我们是否已经改变了initialState
。这很重要,因为如果 initialState
和 newState
完全相同,Redux 不会指示 UI 重新渲染。
注意:如果您尝试以下任何变异解决方案,Immutable.js 会使应用程序崩溃。
从数组中移除元素
不可变方式
const initialState =
members: ['Pete', 'Paul', 'George', 'John']
const reducer = (state, action) =>
switch(action.type)
case 'REMOVE_MEMBER':
return
...state,
members: state.members.filter(
member => member !== action.member
)
const newState = reducer(
initialState,
type: 'REMOVE_MEMBER', member: 'Pete'
);
console.log('initialState', initialState);
console.log('newState', newState);
变异方式
const initialState =
members: ['Pete', 'Paul', 'George', 'John']
const reducer = (state, action) =>
switch(action.type)
case 'REMOVE_MEMBER':
state.members.forEach((member, i) =>
if (member === action.member)
state.members.splice(i, 1)
)
return
...state,
members: state.members
const newState = reducer(
initialState,
type: 'REMOVE_MEMBER', member: 'Pete'
);
console.log('initialState', initialState);
console.log('newState', newState);
向数组添加元素
不可变方式
const initialState =
members: ['Paul', 'George', 'John']
const reducer = (state, action) =>
switch(action.type)
case 'ADD_MEMBER':
return
...state,
members: [...state.members, action.member]
const newState = reducer(
initialState,
type: 'ADD_MEMBER', member: 'Ringo'
);
console.log('initialState', initialState);
console.log('newState', newState);
变异方式
const initialState =
members: ['Paul', 'George', 'John']
const reducer = (state, action) =>
switch(action.type)
case 'ADD_MEMBER':
state.members.push(action.member);
return
...state,
members: state.members
const newState = reducer(
initialState,
type: 'ADD_MEMBER', member: 'Ringo'
);
console.log('initialState', initialState);
console.log('newState', newState);
更新数组
不可变方式
const initialState =
members: ['Paul', 'Pete', 'George', 'John']
const reducer = (state, action) =>
switch(action.type)
case 'UPDATE_MEMBER':
return
...state,
members: state.members.map(member => member === action.member ? action.replacement : member)
const newState = reducer(
initialState,
type: 'UPDATE_MEMBER', member: 'Pete', replacement: 'Ringo'
);
console.log('initialState', initialState);
console.log('newState', newState);
变异方式
const initialState =
members: ['Paul', 'Pete', 'George', 'John']
const reducer = (state, action) =>
switch(action.type)
case 'UPDATE_MEMBER':
state.members.forEach((member, i) =>
if (member === action.member)
state.members[i] = action.replacement;
)
return
...state,
members: state.members
const newState = reducer(
initialState,
type: 'UPDATE_MEMBER', member: 'Pete', replacement: 'Ringo'
);
console.log('initialState', initialState);
console.log('newState', newState);
合并数组
不可变方式
const initialState =
members: ['Paul', 'Ringo']
const reducer = (state, action) =>
switch(action.type)
case 'MERGE_MEMBERS':
return
...state,
members: [...state.members, ...action.members]
const newState = reducer(
initialState,
type: 'MERGE_MEMBERS', members: ['George', 'John']
);
console.log('initialState', initialState);
console.log('newState', newState);
变异方式
const initialState =
members: ['Paul', 'Ringo']
const reducer = (state, action) =>
switch(action.type)
case 'MERGE_MEMBERS':
action.members.forEach(member => state.members.push(member))
return
...state,
members: state.members
const newState = reducer(
initialState,
type: 'MERGE_MEMBERS', members: ['George', 'John']
);
console.log('initialState', initialState);
console.log('newState', newState);
对于经验丰富的开发人员来说,上述改变数组的示例可能看起来像是明显的坏习惯,但对于刚入门的人来说却很容易陷入困境。我们希望任何 Mutated way 代码 sn-ps 都会在代码审查中陷入困境,但情况并非总是如此。 让我们稍微谈谈对象,在您自己处理不变性时比较麻烦。
从对象中移除
不可变方式
const initialState =
members:
paul:
name: 'Paul',
instrument: 'Guitar'
,
stuart:
name: 'Stuart',
instrument: 'Bass'
const reducer = (state, action) =>
switch(action.type)
case 'REMOVE_MEMBER':
let [action.member]: _, ...members = state.members
return
...state,
members
const newState = reducer(
initialState,
type: 'REMOVE_MEMBER', member: 'stuart'
);
console.log('initialState', initialState);
console.log('newState', newState);
变异方式
const initialState =
members:
paul:
name: 'Paul',
instrument: 'Guitar'
,
stuart:
name: 'Stuart',
instrument: 'Bass'
const reducer = (state, action) =>
switch(action.type)
case 'REMOVE_MEMBER':
delete state.members[action.member]
return
...state,
members: state.members
const newState = reducer(
initialState,
type: 'REMOVE_MEMBER', member: 'stuart'
);
console.log('initialState', initialState);
console.log('newState', newState);
更新对象
不可变方式
const initialState =
members:
paul:
name: 'Paul',
instrument: 'Guitar'
,
ringo:
name: 'George',
instrument: 'Guitar'
const reducer = (state, action) =>
switch(action.type)
case 'CHANGE_INSTRUMENT':
return
...state,
members:
...state.members,
[action.key]:
...state.members[action.member],
instrument: action.instrument
const newState = reducer(
initialState,
type: 'CHANGE_INSTRUMENT', member: 'paul', instrument: 'Bass'
);
console.log('initialState', initialState);
console.log('newState', newState);
变异方式
const initialState =
members:
paul:
name: 'Paul',
instrument: 'Guitar'
,
ringo:
name: 'George',
instrument: 'Guitar'
const reducer = (state, action) =>
switch(action.type)
case 'CHANGE_INSTRUMENT':
state.members[action.member].instrument = action.instrument
return
...state,
members: state.members
const newState = reducer(
initialState,
type: 'CHANGE_INSTRUMENT', member: 'paul', instrument: 'Bass'
);
console.log('initialState', initialState);
console.log('newState', newState);
如果你做到了这一点,恭喜!我知道这是一篇冗长的文章,但我觉得展示所有变异方式很重要,您需要在没有 Immutable.js 的情况下防止自己发生这种情况。使用 Immutable.js 的一大优势,除了可以防止您编写糟糕的代码之外,还有帮助方法,例如 mergeDeep
和 updateIn
不可变的.JS
合并深度
const initialState = Immutable.fromJS(
members:
paul:
name: 'Paul',
instrument: 'Guitar'
,
ringo:
name: 'George',
instrument: 'Guitar'
)
const reducer = (state, action) =>
switch (action.type)
case 'ADD_MEMBERS':
return state.mergeDeep(members: action.members)
const newState = reducer(
initialState,
type: 'ADD_MEMBERS',
members:
george: name: 'George', instrument: 'Guitar' ,
john: name: 'John', instrument: 'Guitar'
);
console.log('initialState', initialState);
console.log('newState', newState);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.min.js"></script>
更新在
const initialState = Immutable.fromJS(
members:
paul:
name: 'Paul',
instrument: 'Guitar'
,
ringo:
name: 'George',
instrument: 'Guitar'
)
const reducer = (state, action) =>
switch (action.type)
case 'CHANGE_INSTRUMENT':
return state.updateIn(['members', action.member, 'instrument'], instrument => action.instrument)
const newState = reducer(
initialState,
type: 'CHANGE_INSTRUMENT', member: 'paul', instrument: 'Bass'
);
console.log('initialState', initialState);
console.log('newState', newState);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.min.js"></script>
【讨论】:
是的,但是扩展语法不是实现了 immutable.js 试图实现的目标吗?我很困惑为什么 immutable.js 背后有这么多的炒作和支持,而使用扩展运算符可以实现类似的东西 @YoungMoon,查看我的更新答案,详细了解如何使用纯 ES6 实现不变性。 忘记感谢您提供最棒的答案。谢谢【参考方案2】:我用 Redux 设置状态的方式不是不可变的吗?
在您的示例代码中(假设传递给 filter
的真实函数没有进行任何突变),是的。
使用 immutable.js OVER 扩展运算符方式使对象不可变有什么好处?
两个主要原因:
意外地改变不可变集合对象是不可能的,因为公共 API 不允许这样做。而对于内置的 JS 集合,它是。深度冻结(递归调用Object.freeze
)可以对此有所帮助。
高效* 使用内置集合的不可变更新可能具有挑战性。 Immutable.js 在内部使用 tries 来使更新比原生集合的原生集合更有效。
如果您想使用内置集合,请考虑使用 Immer,它为不可变更新提供了更好的 API,同时还冻结了它创建的对象,有助于缓解第一个问题(但不是第二个问题)。
* Efficient 表示时间复杂度,例如由于对象流失增加,对象构造和 GC 运行。
【讨论】:
以上是关于传播运算符与 immutable.js的主要内容,如果未能解决你的问题,请参考以下文章