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
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.
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.
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)
