如何在 React 中深度克隆对象?

IT技术 javascript reactjs
2021-05-03 20:11:07
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

4个回答

你需要做一个深拷贝。Lodash 的 cloneDeep使这变得简单:

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

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

浅层克隆是克隆了其原始属性但其 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。那不是布埃诺。

简而言之:诸如 NaN 或 Infinity 之类的“更智能”值存在问题,这些值会被 JSON API 转换为 null。如果您使用:方法、RegExps、Maps、Sets、Blob、FileLists、ImageDatas、稀疏数组、Typed Arrays 作为原始对象的属性,还有更多问题。

简而言之。如果你告诉我你是一名中级 JS 开发人员,并且你建议我通过 JSON API 制作对象的深层副本,你将获得初级职位。如果你自称是大四学生并认为 JSON API 是正确答案,我会告诉你面试结束了 :)

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

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

  • 您编写自己的深拷贝实现。我喜欢你,但是当我们有截止日期时请不要这样做。
  • 使用您在项目中已经使用的库或框架提供给您的深度克隆功能。
  • Lodash 的 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。

整个示例可以在这里找到: codeandbox - entire example

尝试使用

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

你实际上在做什么

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

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

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

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