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

时间:2018-02-09 17:06:56

标签: javascript reactjs

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的值

6 个答案:

答案 0 :(得分:27)

您需要制作一份深层副本。 Lodash's cloneDeep让这一切变得简单:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const EdgeInsets _padding = const EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0);
  Color borderColor = Colors.blue;
  bool nameFlag = false;

  @override
  void initState() {
    super.initState();
  }

  void validateName(String value) {
    final RegExp nameExp = new RegExp(r'^[A-Za-z ]+$');
    if (!nameExp.hasMatch(value) || value.isEmpty)
      borderColor = Colors.red;
    else
      borderColor = Colors.blue;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Column(children: <Widget>[
        new Flexible(
          child: new Container(
            margin: _padding,
            padding: _padding,
            child: new TextField(
              decoration: new InputDecoration.collapsed(
                hintText: "Enter Name",
              ),
              onChanged: (s) {
                setState(() => validateName(s));
              },
            ),
            decoration: new BoxDecoration(
              color: Colors.white,
              border: new Border(
                bottom: new BorderSide(color: borderColor, style: BorderStyle.solid),
              ),
            ),
          ),
        )
      ]),
    );
  }
}
祝你好运!

答案 1 :(得分:7)

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

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

请允许我澄清:

  let original = {
    foo: "brlja",
    boo: Date.now(),
    moo: {
      moo: "a cow"
    }
  };

  // shallow copy
  let shallow = Object.assign({}, original);
  console.log(original, shallow); // looks OK

  shallow.moo.moo = "now a cow, my cousin is acting weird";

  console.log(original, shallow); // changing the copy changed the original

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

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

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

什么时候使用浅拷贝?

  • 对象的所有属性都是基元。
  • 你不关心原始对象的复杂属性状态 没有更多(如果您不关心整个对象被更改,请使用它 而不是复制)

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

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

扩展我们原来的例子:

  let falseDeep = JSON.parse(JSON.stringify(original));
  falseDeep.moo.moo = "no really, he needs help";
  console.log(original, shallow, falseDeep); // moo.moo is decoupled

看起来还行吧? 错了! 看看我从一开始就偷偷进入的 datenight 属性发生了什么,你们中的一些人可能会注意到,但假设我只是在炫耀:)

如果你检查它,你会发现它不是同一种类型。 Original 承载 Date 类型,但 falseDeep 承载它的字符串表示。 如果您打算用它做一些数学运算或解析它以将它呈现给用户,那不是 bueno。

如果我们使用 METHODS(OOP 农民用来吓跑光荣的函数式编程大师种族的花哨命名),情况会变得更糟:

// will be converted to {}
JSON.stringify({ key: Symbol() });
JSON.stringify({ key: function(){} });

好吧,现在该死的东西真的击中了粉丝。 “更智能”的值(如 NaN 或 Infinity)存在问题,这些值会被 JSON API 转换为 null。如果您使用,还有其他问题: RegExps、Maps、Sets、Blob、FileLists、ImageDatas、稀疏数组、类型化数组作为原始对象的属性。

简而言之。如果您告诉我您是一名中级 JS 开发人员,并且建议我通过 JSON API 制作对象的深层副本,那么您最多只能获得初级职位。如果您声称自己是大四学生并建议将其作为完整答案,我会告诉您面试结束了 :)

为什么?好吧这会产生一些最讨厌的错误来跟踪那里的错误。。在 Typescript 成为一种东西之前,我做噩梦跟踪 Date 示例。开发工具很好地掩盖了它,所以它看起来“正确”,它甚至自动转换了它的类型。

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

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

可以在此处找到整个示例: codesandbox - entire example

答案 2 :(得分:1)

您实际上在做什么

CREATE ASSEMBLY

是浅表副本,类似于具有传播运算符的let oldMessages = Object.assign({}, this.state.messages);

对象在内存中有自己的引用以销毁它,您可以使用{...this.state.message} 不管它具有多大的嵌套键,它都会删除对象的引用,您将获得一个新对象。

此概念称为深层副本或深层克隆。

答案 3 :(得分:0)

尝试使用

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

答案 4 :(得分:-1)

您也可以这样做:

let message = {id: 1, name: "foo", content: "bar"};

let newMessage = {...message};

https://reactjs.org/docs/jsx-in-depth.html#spread-attributes

答案 5 :(得分:-2)

深度克隆对象的最佳方法之一是使用spread运算符。它可以进行深度克隆,而无需编写任何额外的代码。

例如......

const obj1 = {foo: {baz: 'bar'}}
const obj2 = {test: 'temp}
const obj3 = {...obj1, ...obj2} 
// obj3 = {foo: {baz: 'bar'}, test: 'temp}