传播算子vs immutable.js

时间:2018-11-22 21:01:25

标签: javascript reactjs redux immutable.js

当将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库的重要性。

2 个答案:

答案 0 :(得分:3)

简短回答

是的! ES6传播算子可以完全用作immutable.js的替代者,但是有一个主要警告,您必须始终保持态势感知。

非常长的回答

您和您的开发人员将对维护不变性负100%的责任,而不是让immutable.js为您处理。以下是有关如何使用ES6“扩展运算符”及其filtermap等各种功能自行管理不可变状态的详细信息。

以下内容将探讨如何以不变和变异的方式将值删除和添加到数组或对象。在每个示例中,我将注销initialStatenewState,以说明我们是否对initialState进行了突变。之所以重要,是因为如果initialStatenewState完全相同,则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方法代码段都可以在代码审查中被发现,但并非总是如此。 让我们来谈谈一些对象,这些对象在自己处理不可变性时比较麻烦。

从对象中删除

不变的方式

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);

如果您到目前为止做的不好,恭喜!我知道这是一篇冗长的文章,但是我觉得必须证明所有 Mutated方式(如果您没有Immutable.js则需要使用这些方式来防止自己),这一点很重要。使用Immutable.js的一大优势,不仅可以防止编写错误的代码,还可以使用辅助方法,例如mergeDeepupdateIn

Immutable.JS

mergeDeep

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>

updateIn

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>

答案 1 :(得分:2)

  

我不是用Redux设置状态不变吗?

在您的示例代码中(假设传递给filter的实函数没有做任何突变),是的。

  

使用immutable.js OVER传播算子使对象不可变的好处是什么?

两个主要原因:

  1. 由于公共API不允许,(不容易)意外地更改不可变集合对象。内置JS集合则是。深度冻结(递归调用Object.freeze)可以有所帮助。

  2. 对于内置集合高效使用不可变更新可能是一个挑战。 Immutable.js在内部使用tries来使更新比原始使用原始集合可能更有效。

如果要使用内置集合,请考虑使用Immer,它为不可变更新提供了更好的API,同时还冻结了它创建的对象,从而有助于减轻第一个问题(但不能解决第二个问题)。 / p>

*有效的时间复杂度,例如对象流失增加导致对象构建和GC运行。