避免map()内的Object.assign()造成状态突变

时间:2020-02-28 21:28:55

标签: javascript reactjs redux

在componentDidMount中,我这样做:

     apps.forEach(app => {
            if (chosenAppId) {
                if (app.id === chosenAppId) {
                    this.setState({ map: this.props.map });
                }
            }
        });

现在,当我在某些功能中执行此操作时:

 this.setState({
            ...this.state.map,
            areas: this.state.map.areas.map(el =>
                el._id === area._id
                    ? Object.assign(el, {
                          chooseDevice: false,
                          editModal: true
                      })
                    : Object.assign(el, { chooseDevice: false })
            )
        });

我具有持久的redux状态,在这种情况下,我希望重新加载this.state.map === this.props.map,但是不知何故,此object.assign改变了我的redux状态,重新加载时,所有内容都保存到了reducer中。

我将其范围缩小为与object.assign有关,因为如果我将.concat()与this.state.map相关,则不会更改redux状态。

如何?我真的不明白。没有调度redux操作,不知道如何发生。

4 个答案:

答案 0 :(得分:3)

Object.assign(el, { chooseDevice: false })行将使el突变。

似乎您已将其从道具(因此可能是Redux商店)复制到状态中。因此,它与Redux商店中已经存在的对象引用相同,因此您正在变异商店中的值。

请注意,our official Redux Toolkit package默认包含一个突变检测中间件,当您意外地突变值时,它将引发错误。

答案 1 :(得分:1)

您对问题的根本原因完全正确-Object.assign()正在突变您在map()中引用的原始数组项。

要解决此问题,只需摆脱Object.assign()来改变您的状态:

 this.setState({
    ...this.state.map,
    areas: this.state.map.areas.map(el => ({
       ...el,
       chooseDevice: false,
       ...(el._id === area.id && {editModal: true})
    }))
 });

答案 2 :(得分:1)

散布一个对象时,它不会克隆现有属性,它只会将它们复制到新对象中,这意味着实例属性将保持不变。

    const obj = { numbers: [1, 2, 3], person: { name: 'Foo' } }
    const copy = { ...obj };
    copy.numbers.push(4);
    copy.person.name = 'Bar'
    console.log(obj.numbers) // [1,2,3,4]
    console.log(obj.person) // { name: 'Bar' }
    console.log(copy.numbers) // [1,2,3,4]
    console.log(copy.person) // { name: 'Bar' }

请注意,通过更改副本来更新原始对象

因此,当您将州扩展到本地州时,即

 ...this.state.map

然后在实例属性上使用Object.assign,您将无意中同时更新Redux状态。

答案 3 :(得分:1)

使用Object.assign()

Object.assign()方法将所有可枚举的自身属性从一个或多个 source 对象复制到 target 对象。

Object.assign(target, ...sources)

目标

目标对象-修改源属性后将对其应用源属性的对象。

来源

源对象—包含要应用的属性的对象。

Object.assign() - JavaScript | MDN

如果您不希望更改提供的元素,请定位一个空对象。这样,el和第三个参数中的其他数据将被分配给新对象,而不是覆盖el的属性。

this.setState(prevState => ({
    ...prevState.map,
    areas: prevState.map.areas.map(el =>
        el._id === area._id
            ? Object.assign({}, el, {
                  chooseDevice: false,
                  editModal: true
              })
            : Object.assign({}, el, { chooseDevice: false })
    )
}));

对象扩散(紧凑)

如果您想使它更紧凑,还可以将其转换为对象散布,如果editModal中的条件更改,请使用内联。

this.setState(prevState => ({
    ...prevState.map,
    areas: prevState.map.areas.map(el => ({
        ...el,
        chooseDevice: false,
        editModal: el._id === area._id ? true : el.editModal
    }))
}));

编辑this.state不应在setState中使用