【问题标题】:Am I mutating the redux state in reducer?我在reducer中改变redux状态吗?
【发布时间】:2018-09-19 01:22:59
【问题描述】:

我正在尝试使用 reducer 在我的状态下更改数组中的一项。

状态如下:

state: {
  items: [
  {
    id: 1,
    name: 'Superman',
    wearsCape: true
  },
  {
    id: 2,
    name: 'Batman',
    wearsCape: true
  },
  {
    id: 3,
    name: 'Iron Man',
    wearsCape: false
  }
];
}

我正在尝试过滤状态中的所有项目以搜索名称等于 sHero.name 的超级英雄的首次出现。然后我试图改变找到的超级英雄的属性。 reducer 代码如下所示:

function findCapedCrusader(state, { sHero }) {
  var result = state.items.filter(s => {
    return s.name === sHero.name;
  });

  result[0].wearsCape = false;

  return { ...state };

}

我是否通过执行上述操作来改变状态??

注意:为愚蠢的例子道歉。我是 react 和 redux 的新手。

【问题讨论】:

  • 否,因为.filter().map() 一样,返回一个全新的数组。
  • 其实 OP 会改变原始状态。 .filter 返回相同的引用对象,并且属性发生了变化。

标签: javascript reactjs redux immutability


【解决方案1】:

据我所知,filter 方法返回一个新数组,但返回的元素仍然引用旧数组。因此,如果有人将属性更改为新创建的数组,他们也会更改原始数组。

const state = {
  items: [
  {
    id: 1,
    name: 'Superman',
    wearsCape: true
  },
  {
    id: 2,
    name: 'Batman',
    wearsCape: true
  },
  {
    id: 3,
    name: 'Iron Man',
    wearsCape: false
  }
]
};

const newItem = state.items.filter( s => s.name === "Superman");

newItem[0].wearsCape = false;

console.log( "newitem", newItem, "\noriginal", state.items[0] );

如果你想改变数组中一个对象的属性并更新你的状态:

const state = {
  items: [
  {
    id: 1,
    name: 'Superman',
    wearsCape: true
  },
  {
    id: 2,
    name: 'Batman',
    wearsCape: true
  },
  {
    id: 3,
    name: 'Iron Man',
    wearsCape: false
  }
]
};

const newItems = state.items.map( s => {
  if( s.name === "Superman") {
    return { ...s, wearsCape: false };
  }
  return s;
});

console.log( "new items", newItems, "original", state.items );

如您所见,原始的items 没有发生变异。对于您的情况,它将是这样的:

function findCapedCrusader(state, { sHero }) {
  var newItems = state.items.map(s => {
    if (s.name === sHero.name ) {
        return { ...s, wearsCape = false }
    }
    return s;
  });
  return { ...state, items: newItems };
}

同样,此函数将更改一个对象的属性,而不会更改其他任何内容。我不太确定这是您想要的,因为您在问题中尝试使用 filter。如果你想用一个对象更新你的状态,我可以建议另一种解决方案。

【讨论】:

    【解决方案2】:

    filter() 方法正在返回一个新的数组实例,因此您不会改变您的状态。更多信息,请参阅MDN documentation

    返回值

    一个新数组,每个元素都是回调函数的结果。

    【讨论】:

      【解决方案3】:

      你是。即使过滤器函数会生成一个新数组,状态也存在于您在该新数组中返回的对象中。基于此,您正在更改状态,这不符合纯函数是什么。

      在这里,我给你留下一个很好的帖子,它可以帮助你更好地理解什么是深拷贝以及如何完成它。

      https://medium.com/@tkssharma/objects-in-javascript-object-assign-deep-copy-64106c9aefab

      【讨论】:

        【解决方案4】:

        是的,在这个例子中你正在改变你的状态。虽然filter 确实生成了一个新数组,但它实际上只是原始数组的浅拷贝。 state.items 数组的元素是对象,因此当您在新的 results 数组(包含指向 state.items 的原始内容的指针)中修改一个时,您会修改原始对象。有关深拷贝和浅拷贝的更多信息,请参阅此答案:What is the difference between a shallow copy and a deep copy with JavaScript arrays?

        要解决此问题,您可以:

        1.result 上使用扩展运算符,然后像这样覆盖wearsCape 的值:

        result = state.items.filter(s => s.name === sHero.name).map(s => {
            return {...s, wearsCape: false}
        });
        

        此方法适用于可序列化和不可序列化数据,是更新嵌套状态值的推荐方法。

        或者

        2.在过滤之前对state.items进行深拷贝:

        const results = JSON.parse(JSON.stringify(state.items)).filter(s => {
            return s.name === sHero.name;
        });
        

        这种方法有点奇怪,但是对于可序列化的数据来说效果很好,因为你要修改的值嵌套很深,并且多次使用扩展运算符 (...) 会变得很麻烦。

        【讨论】:

        • 或使用newState = Object.assign({}, state) 对您的原始状态进行深层复制。
        • @nbwoodward,实际上也是Object.assign,制作浅拷贝。 See here.
        • @devserkan 哦,哇,很有趣。 Stringify/parse 似乎是一个非常老套的解决方案。我几乎总是最终使用带有 redux 的 ImmutableJS 库来避免此类问题的可能性。
        • @nbwoodward,是的,它真的很hacky,也不是我的解决方案:D 除此之外,如果您的对象不可序列化,这种方式也不起作用。最好使用其他方法并以某种方式更新对象而不改变它。
        猜你喜欢
        • 1970-01-01
        • 2023-03-23
        • 2019-02-03
        • 1970-01-01
        • 1970-01-01
        • 2018-08-09
        • 1970-01-01
        • 1970-01-01
        • 2018-04-30
        相关资源
        最近更新 更多