【问题标题】:How do I deep clone an object in React?如何在 React 中深度克隆对象?
【发布时间】:2018-02-09 17:06:56
【问题描述】:
let oldMessages = Object.assign({}, this.state.messages);
// this.state.messages[0].id = 718

console.log(oldMessages[0].id);
// Prints 718

oldMessages[0].id = 123;

console.log(this.state.messages[0].id);
// Prints 123

如何防止oldMessages成为参考,我想改变oldMessages的值而不改变state.messages的值

【问题讨论】:

标签: javascript reactjs


【解决方案1】:

您需要进行深层复制。 Lodash's cloneDeep 让这一切变得简单:

import cloneDeep from 'lodash/cloneDeep';
const oldMessages = cloneDeep(this.state.messages);
oldMessages[0].id = 123;

【讨论】:

    【解决方案2】:

    首先让我们澄清一下浅克隆和深克隆的区别:

    浅克隆是克隆了其原始属性但其 REFERENCE 属性仍引用原始属性的克隆。

    请允许我澄清一下:

    let original = {
      foo: "brlja",
      howBigIsUniverse: Infinity,
      mrMethodLookAtMe: () => "they call me mr. Method",
      moo: {
       moo: "MOO"
      }
    };
    
      // shallow copy
      let shallow = Object.assign({}, original);
      console.log(original, shallow); // looks OK
    
      shallow.moo.moo = "NOT MOO";
    
      console.log(original, shallow); // changing the copy changed the original
    

    注意如何改变浅拷贝的非原始属性的内部属性反映在原始对象上。

    那么我们为什么要使用浅拷贝呢?

    • 绝对更快。
    • 可以通过 1 行在纯 JS 中完成。

    什么时候使用浅拷贝?

    • 对象的所有属性都是基元
    • 您正在制作部分副本,其中所有复制的属性都是基元
    • 您不关心原件的命运(是否有理由复制而不使用原件?)

    好的,让我们开始制作一个正确的(深层)副本。深拷贝显然应该将原始对象按值而不是引用处理到克隆中。当我们深入研究对象时,这应该会持续存在。因此,如果我们在原始属性中获得 X 级深度嵌套对象,它仍然应该是副本,而不是内存中对同一事物的引用。

    大多数人的建议是滥用 JSON API。他们认为将一个对象变成一个字符串,然后通过它返回一个对象会产生一个深拷贝。嗯,是的,也不是。让我们尝试这样做。

    扩展我们原来的例子:

      let falseDeep = JSON.parse(JSON.stringify(original));
      falseDeep.moo.moo = "HEY I CAN MOO AGAIN";
      console.log(original, falseDeep); // moo.moo is decoupled
    

    看起来不错,对吧? 错了! 看看我从一开始就潜入的 mrMethodLookAtMehowBigIsUniverse 属性发生了什么:)

    一个返回null,它绝对不是Infinity,另一个是GONE。那不是bueno。

    简而言之:“更智能”的值(例如 NaN 或 Infinity)会被 JSON API 变为 null。如果您使用,还有其他问题: 方法、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、稀疏数组、类型化数组作为原始对象的属性。

    简而言之。如果您告诉我您是中级 JS 开发人员,并且您建议我通过 JSON API 对对象进行深度复制,您将获得初级职位。如果你声称自己是一名大四学生并建议 JSON API 是正确答案,我会告诉你面试已经结束:)

    为什么?嗯这会产生一些最讨厌的跟踪错误。。在 Typescript 成为事物之前,我一直在做噩梦,跟踪消失的方法或类型被转换为另一个(通过了某人的错误输入参数检查但随后无法产生有效结果)。

    是时候总结一下了!那么正确答案是什么?

    • 您编写自己的深拷贝实现。我喜欢你,但是当我们有最后期限时,请不要这样做。
    • 使用您已在项目中使用的库或框架为您提供的深度克隆功能。
    • Lo​​dash 的 cloneDeep

    很多人仍然使用 jQuery。所以在我们的例子中(请把 import 放在它所属的地方,在文件的顶部):

    import jQ from "jquery"; 
    let trueDeep = jQ.extend(true, original, {});
    console.log(original, trueDeep);
    

    这很有效,它是一个很好的深层副本,并且是单行的。但是我们必须导入整个 jQuery。对于一些它已经存在,但对我来说,我倾向于避免它,因为它过于臃肿并且命名非常不一致。

    Angular 用户可以使用angular.copy()。对于您的项目依赖项,如果它在那里,您可以 google :)

    但是如果我的框架/库没有类似的功能怎么办?

    您可以在 JS 库中使用我个人的 SUPERSTAR(我不参与该项目,只是一个忠实的粉丝)- Lodash(或 _ 为朋友)。

    因此扩展我们的示例(再次注意导入的位置):

    import _ from "lodash"; // cool kids know _ is low-dash
    var fastAndDeepCopy = _.cloneDeep(objects);
    console.log(original, lodashDeep);
    

    这是一个简单的oneliner,它可以工作,而且速度很快。

    差不多就是这样:)

    现在你知道 JS 中浅拷贝和深拷贝的区别了。您意识到 JSON API 滥用就是这样,滥用而不是真正的解决方案。如果您已经在使用 jQuery 或 Angular,那么您现在知道已经有适合您的解决方案了。如果没有,您可以自己编写或考虑使用 lodash。

    完整的例子可以在这里找到: codesandbox - entire example

    【讨论】:

    • 漂亮的答案,一件事是在浏览器中,滥用 JSON API 后日期看起来没有区别(都是时间戳),但我不明白为什么这是一种滥用,可能会改变如您最后所说,将 Date 示例转换为空类 ou 函数
    • @SpaceDogCS Ty 进行编辑和您的友好评论。现在我想起来,滥用会是一个更好的词。 “(都是时间戳)”,看起来这取决于我在控制台中看到的不同浏览器,并尝试用一个来做数学运算,但另一个会破坏。我可以在其中编辑更多内容,但我列了一个列表(并将其加粗),我的总体意图是解释你不应该这样做,而不是在注意这个陷阱列表的同时给出你应该如何做的印象。可能是我错过了该列表中的某些内容,但这不是重点:)
    • 如果您知道您的层次结构永远不会包含字符串和整数以外的任何内容,那么使用 JSON api 似乎是完全可以接受的。编写一个滥用 JSON api 的抽象函数然后重写它并在需要时使用更重的深度复制方法并不难。只要它是分开的,如果您需要担心可能会破坏它的扩展,那就太好了。另外,我想深拷贝方法比 JSON 解析慢得多,还是我错了?真正的深拷贝需要递归整个结构。有 JSON 吗?
    • 我很难理解“花哨的命名 OOP 农民用来吓跑光荣的函数式编程大师赛”,所以它的意思是“方法,是对象或类中的函数”
    • 所有 cmets 的朋友们,我已经重新审视了答案并通过使其以更少的代码和文字显示更多的问题来改进它。此外,我已经删除了何时使用浅拷贝的另一个选项,因为我认为随着代码库的老化,它带来的麻烦多于好处。
    【解决方案3】:

    尝试使用

    let tempVar = JSON.parse(JSON.stringify(this.state.statename))
    

    【讨论】:

    • 请解释为什么这是其他答案未涵盖的答案。
    • 虽然这可行,但我怀疑如果您的数据结构不能用 JSON 表示(例如您的键之一是日期对象),它会遇到问题。
    • 这很不干净,速度很慢,如果 JSON 序列化/反序列化跳过某些部分可能会出现问题。
    • 请不要。 JSON API 不打算用于此目的,并且此方法存在许多缺陷。请检查我的答案并了解如何正确执行。
    【解决方案4】:

    你实际上在做什么

    let oldMessages = Object.assign({}, this.state.messages);
    

    是一个浅拷贝,类似于带有扩展运算符的{...this.state.message}

    对象在内存中有自己的引用来销毁它,你可以使用JSON.parse (JSON.stringify(object)) 不管它有多么嵌套的键,它都会删除对象的引用,你会得到一个新的对象。

    这个概念称为深拷贝或深克隆。

    【讨论】:

    • 不要使用 JSON API 来制作数据的深层副本(尤其是如果您不知道所述数据的外观)。我已经解释了这会导致的许多陷阱。
    猜你喜欢
    • 2010-09-09
    • 2019-08-09
    • 1970-01-01
    • 2012-06-05
    • 2013-09-06
    • 2010-10-15
    • 1970-01-01
    相关资源
    最近更新 更多