Pesquisa de site

javascript - typescript - clonando objeto


Eu tenho uma superclasse que é a pai (Entity) de muitas subclasses (Customer, Product, ProductCategory...)

Estou procurando clonar dinamicamente um objeto que contém diferentes subobjetos em Typescript.

No exemplo: um Customer que tem diferentes Product que possuem uma ProductCategory

var cust:Customer  = new Customer ();

cust.name = "someName";
cust.products.push(new Product(someId1));
cust.products.push(new Product(someId2));

Para clonar toda a árvore de objetos, eu criei uma função em Entity

public clone():any {
    var cloneObj = new this.constructor();
    for (var attribut in this) {
        if(typeof this[attribut] === "object"){
           cloneObj[attribut] = this.clone();
        } else {
           cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

O new gera o seguinte erro quando é transpilado para javascript: error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.

Embora o script funcione, eu gostaria de me livrar do erro transpilado

Resposta #1

Resolvendo o Problema Específico

Você pode usar uma asserção de tipo para dizer ao compilador que você sabe melhor:

public clone(): any {
    var cloneObj = new (this.constructor() as any);
    for (var attribut in this) {
        if (typeof this[attribut] === "object") {
            cloneObj[attribut] = this[attribut].clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

Clonagem

A partir de 2022, há uma proposta para permitir que structuredClone copie profundamente muitos tipos.

const copy = structuredClone(value)

Existem algumas limitações sobre que tipo de coisa você pode usar isso.

Tenha em mente que às vezes é melhor escrever seu próprio mapeamento - em vez de ser totalmente dinâmico. No entanto, existem alguns truques de "clonagem" que você pode usar que proporcionam efeitos diferentes.

Usarei o seguinte código para todos os exemplos subsequentes:

class Example {
  constructor(public type: string) {

  }
}

class Customer {
  constructor(public name: string, public example: Example) {

  }

  greet() {
    return 'Hello ' + this.name;
  }
}

var customer = new Customer('David', new Example('DavidType'));

Opção 1: Espalhar

Propriedades: Sim
Métodos: Não
Cópia Profunda: Não

var clone = { ...customer };

alert(clone.name + ' ' + clone.example.type); // David DavidType
//alert(clone.greet()); // Not OK

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

Opção 2: Object.assign

Propriedades: Sim
Métodos: Não
Cópia Profunda: Não

var clone = Object.assign({}, customer);

alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // Not OK, although compiler won't spot it

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

Opção 3: Object.create

Propriedades: Herdadas
Métodos: Herdados
Cópia Profunda: Herdada Superficial (alterações profundas afetam tanto o original quanto o clone)

var clone = Object.create(customer);
    
alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // OK

customer.name = 'Misha';
customer.example = new Example("MishaType");

// clone sees changes to original 
alert(clone.name + ' ' + clone.example.type); // Misha MishaType

clone.name = 'Steve';
clone.example.type = 'SteveType';

// original sees changes to clone
alert(customer.name + ' ' + customer.example.type); // Misha SteveType

Opção 4: Função de Cópia Profunda

Propriedades: Sim
Métodos: Não
Cópia Profunda: Sim

function deepCopy(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = deepCopy(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

var clone = deepCopy(customer) as Customer;

alert(clone.name + ' ' + clone.example.type); // David DavidType
// alert(clone.greet()); // Not OK - not really a customer

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David DavidType

Resposta #2

  1. Use o operador spread ...

     const obj1 = { param: "value" };
     const obj2 = { ...obj1 };
    

O operador spread pega todos os campos de obj1 e os espalha sobre obj2. No resultado, você obtém um novo objeto com nova referência e os mesmos campos do original.

Lembre-se de que é uma cópia superficial, o que significa que, se o objeto for aninhado, seus parâmetros compostos aninhados existirão no novo objeto pela mesma referência.

  1. Object.assign()
     const obj1={ param: "value" };
     const obj2:any = Object.assign({}, obj1);
    

Object.assign cria uma cópia real, mas apenas das propriedades próprias, então propriedades no protótipo não existirão no objeto copiado. Também é uma cópia superficial.


  1. Object.create()
     const obj1={ param: "value" };
     const obj2:any = Object.create(obj1);
    

Object.create não faz clonagem real, ele está criando um objeto a partir do protótipo. Então use-o se o objeto deve clonar propriedades de tipo primitivo, porque a atribuição de propriedades de tipo primitivo não é feita por referência.

As vantagens do Object.create são que quaisquer funções declaradas no protótipo estarão disponíveis em nosso objeto recém-criado.


Algumas coisas sobre cópia superficial

A cópia superficial coloca em um novo objeto todos os campos do antigo, mas isso também significa que, se o objeto original tiver campos de tipo composto (objetos, arrays etc.), esses campos serão colocados no novo objeto com as mesmas referências. A mutação de tal campo no objeto original será refletida no novo objeto.

Pode parecer uma armadilha, mas na verdade a situação em que todo o objeto complexo precisa ser copiado é rara. A cópia superficial reutilizará a maior parte da memória, o que significa que é muito barata em comparação com a cópia profunda.


Cópia profunda

O operador spread pode ser útil para cópia profunda.

const obj1 = { param: "value", complex: { name: "John"}}
const obj2 = { ...obj1, complex: {...obj1.complex}};

O código acima criou uma cópia profunda de obj1. O campo composto "complex" também foi copiado para obj2. A mutação do campo "complex" não será refletida na cópia.

Resposta #3

Tente isto:

let copy = (JSON.parse(JSON.stringify(objectToCopy)));

É uma boa solução até você estar usando objetos muito grandes ou seu objeto tiver propriedades não serializáveis.

Para preservar a segurança de tipo, você poderia usar uma função de cópia na classe da qual deseja fazer cópias:

getCopy(): YourClassName{
    return (JSON.parse(JSON.stringify(this)));
}

ou de forma estática:

static createCopy(objectToCopy: YourClassName): YourClassName{
    return (JSON.parse(JSON.stringify(objectToCopy)));
}

Resposta #4

TypeScript/JavaScript possui seu próprio operador para clonagem superficial:

let shallowClone = { ...original };

Resposta #5

Para clonagem profunda serializável, com Informação de Tipo é,

export function clone<T>(a: T): T {
  return JSON.parse(JSON.stringify(a));
}

Resposta #6

É fácil obter uma cópia superficial com o "Object Spread" introduzido no TypeScript 2.1

este TypeScript: let copy={ ...original };

produz este JavaScript:

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var copy = __assign({}, original);

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

Resposta #7

Aqui está a implementação de deepCopy em TypeScript (nenhum any está incluído no código):

const deepCopy = <T, U = T extends Array<infer V> ? V : never>(source: T ): T => {
  if (Array.isArray(source)) {
    return source.map(item => (deepCopy(item))) as T & U[]
  }
  if (source instanceof Date) {
    return new Date(source.getTime()) as T & Date
  }
  if (source && typeof source === 'object') {
    return (Object.getOwnPropertyNames(source) as (keyof T)[]).reduce<T>((o, prop) => {
      Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop)!)
      o[prop] = deepCopy(source[prop])
      return o
    }, Object.create(Object.getPrototypeOf(source)))
  }
  return source
}

Resposta #8

Adicione "lodash.clonedeep": "^4.5.0" ao seu package.json. Então use assim:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

Artigos relacionados