在React中setState期间克隆对象/数组的正确方法

时间:2017-12-03 22:41:24

标签: javascript reactjs

我从:

开始
constructor() {
   super();
      this.state = {
         lists: ['Dogs','Cats'], 
         items: {Dogs: [{name: "Snoopy"}, {name: "Lola"}, {name: "Sprinkles"}], 
                 Cats: [{name: "Felidae"}, {name: "Garfiled"}, {name: "Cat in the Hat"}] }             
   };

}

然后我有我的addItem函数:

handleAddItem(s) {      

  var key = Object.keys(s)[0];
  var value = s[key];

  var allItems = {...this.state.items};

      allItems[key].push({name: value});    

      this.setState({items: allItems});
}

在其他地方我将s定义为:

var s={};
   s[this.props.idName] = this.refs.id.value;

这有效,但我想知道这是否是将元素添加到项目中的一个键中的正确方法。 AllItems真的指向this.state.items,我认为它应该是一个深层复制,但我不知道该怎么做。

看起来item是一个保存键/值对的对象,其中值是数组。那是对的吗?我在哪里可以学习如何操纵这样的结构?

4 个答案:

答案 0 :(得分:6)

我个人依赖这种深层复制策略。 JSON.parse(JSON.stringify(object))而不是spread运算符,因为它在处理嵌套对象或多维数组时遇到了奇怪的错误。

如果我是正确的,

spread运算符不会执行深层复制,并且会在React中使用 NESTED 对象导致状态突变。

请仔细阅读代码,以便更好地了解两者之间的情况。想象一下,这是使用spread运算符进行变异的状态变量。



const obj = {Dogs: [{name: "Snoopy"}, {name: "Lola"}, {name: "Sprinkles"}], Cats: [{name: "Felidae"}, {name: "Garfiled"}, {name: "Cat in the Hat"}] };

const newObj = {...obj};
console.log("BEFORE SPREAD COPY MUTATION")

console.log("NEW OBJ: " + newObj.Dogs[0].name); //Snoopy
console.log("OLD OBJ: " + obj.Dogs[0].name); //Snoopy

newObj.Dogs[0].name = "CLONED Snoopy";

console.log("AFTER SPREAD COPY MUTATION")

console.log("NEW OBJ: " + newObj.Dogs[0].name); // CLONED Snoopy
console.log("OLD OBJ: " + obj.Dogs[0].name); // CLONED Snoopy

// Even after using the spread operator the changed on the cloned object are affected to the old object. This happens always in cases of nested objects.

// My personal reliable deep copy

console.log("*********DEEP COPY***********");

console.log("BEFORE DEEP COPY MUTATION")
deepCopyObj = JSON.parse(JSON.stringify(obj));


console.log("NEW OBJ: " + newObj.Dogs[0].name); //CLONED Snoopy
console.log("OLD OBJ: " + obj.Dogs[0].name); // CLONED Snoopy
console.log("DEEP OBJ: " + deepCopyObj.Dogs[0].name); //CLONED Snoopy


deepCopyObj.Dogs[0].name = "DEEP CLONED Snoopy";

console.log("AFTER DEEP COPY MUTATION")
console.log("NEW OBJ: " + newObj.Dogs[0].name); // CLONED Snoopy
console.log("OLD OBJ: " + obj.Dogs[0].name); // CLONED Snoopy
console.log("DEEP OBJ: " + deepCopyObj.Dogs[0].name); // DEEP CLONED Snoopy




现在,如果您想对对象执行深层复制,请将处理程序更改为此

handleAddItem(s) {      

  var key = Object.keys(s)[0];
  var value = s[key];

  var allItems = JSON.parse(JSON.stringify(this.state.items));

      allItems[key].push({name: value});    

      this.setState({items: allItems});
}

答案 1 :(得分:1)

一个问题可能是var allItems = {...this.state.items};只会执行this.state.items的浅层克隆。因此,当您将数据推送到此数组时,它会在您调用setState之前更改现有数组。

您可以使用Immutable.js来解决此问题。

import { List, fromJS, Map } from 'immutable';

constructor() {
   super();
      this.state = {
       lists: List(['Dogs','Cats']), 
       items: fromJS({
        Dogs: [
          { name: "Snoopy" },
          ...
        ],
        Cats: [
          { name: "Felidae" },
          ...
        ]
      })
   };
}

然后你的添加功能如下:

handleAddItem(s) {      
  var key = Object.keys(s)[0];
  var value = s[key];

  var allItems = this.state.items.set(key, Map({ name: value }));
  this.setState({ items: allItems });
}

只是一个想法!

答案 2 :(得分:0)

我想添加更多有关克隆数组的信息。您可以调用slice,将0作为第一个参数:

const clone = myArray.slice(0);

上面的代码创建原始数组的克隆;请记住,如果对象存在于数组中,则保留引用;也就是说,上面的代码并未对数组内容进行“深层”克隆。

答案 3 :(得分:0)

选择的答案是对Nandu Kalidindi的一个奇迹和巨大的支持,我在项目中正在更新的对象内部存在嵌套数组的错误,并且不了解“深度”或“嵌套”数组和对象的概念不会被复制到新对象。

这是我对他的修正,使它看起来对我更具吸引力,并且效果同样好!

我利用lodash并发现了它们的_.cloneDeep()功能来满足我的更新状态需求。

Lodash _.cloneDeep()

我了解到,映射出更新的数组以防止任何错误是解决此类问题的最佳方法,但是我正在克隆带有嵌套数组的整个对象,而嵌套数组会改变状态中旧对象的数组。

这是我的答案。


const state = {
    fields: {
        "caNyDcSDVb": {
            id: "caNyDcSDVb",
            name: "test1",
            type: "text",
            options: ["A", "B", "C"]
        }
    },
};

const FieldCopy = (id) => {
    const newCopiedField = _.cloneDeep(state.fields[id]);
    newCopiedField.id = nanoid(10);
    return {
        ...state,
        fields: {
            ...state.fields,
            newCopiedField[id]: newCopiedField
        }
    };
};