【问题标题】:spread operator vs immutable.js传播运算符与 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 库而不是点运算符的重要性。

【问题讨论】:

    标签: javascript reactjs redux immutable.js


    【解决方案1】:

    简答

    是的! 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 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 的一大优势,除了可以防止您编写糟糕的代码之外,还有帮助方法,例如 mergeDeepupdateIn

    不可变的.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 扩展运算符方式使对象不可变有什么好处?

    两个主要原因:

    1. 意外地改变不可变集合对象是不可能的,因为公共 API 不允许这样做。而对于内置的 JS 集合,它是。深度冻结(递归调用Object.freeze)可以对此有所帮助。

    2. 高效* 使用内置集合的不可变更新可能具有挑战性。 Immutable.js 在内部使用 tries 来使更新比原生集合的原生集合更有效。

    如果您想使用内置集合,请考虑使用 Immer,它为不可变更新提供了更好的 API,同时还冻结了它创建的对象,有助于缓解第一个问题(但不是第二个问题)。

    * Efficient 表示时间复杂度,例如由于对象流失增加,对象构造和 GC 运行。

    【讨论】:

      猜你喜欢
      • 2017-01-06
      • 1970-01-01
      • 2017-12-09
      • 2015-02-26
      • 2017-11-15
      • 2016-03-14
      • 2019-01-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多