web-dev-qa-db-fra.com

Deep clone in TypeScript (préservation des types)

J'ai besoin de cloner en profondeur un objet dans TypeScript. Cela ne devrait pas poser de problème, car des bibliothèques telles que Lodash fournissent des fonctions appropriées. Cependant, ceux-ci semblent rejeter les informations de type.

> var a = new SomeClass();
> a instanceof SomeClass;
< true
> var b = _.cloneDeep(a);
> b instanceof SomeClass;
< false

Existe-t-il un moyen de cloner des objets dans TypeScript tout en préservant ces informations de frappe?

8
Julian B

TypeScript ne supprime pas les informations de type ici. Dans le fichier DefinitelyTyped lodash.d.ts , vous pouvez voir que cloneDeep est défini comme 

cloneDeep<T>(
    val: T,
    customizer?: (value: any) => any,
    thisArg?: any
) : T

En ignorant les arguments qui nous importent, il faut une T en entrée et un T en sortie. Donc, TypeScript ne perd aucune information de type; il considère que la sortie de cloneDeep est du même type que l'entrée.

Vous devriez pouvoir vérifier cela via votre éditeur: en supposant que vous ayez un éditeur qui vous permette d'inspecter le type de variables ou de méthodes d'auto-complétion (ce que je vous recommande vivement, si vous ne le faites pas).


Pourquoi alors la typeof ne fonctionne-t-elle pas comme prévu? C'est parce que les informations de type TypeScript ne sont pas transférées à l'exécution. instanceof est un opérateur JS natif, dont TypeScript ne modifie pas le comportement, que vous pouvez voir en exécutant cet extrait:

"use strict";
class A {}

let a = new A();
let b = _.cloneDeep(a);

if (b instanceof A) {
  alert("b is an instance of A");
} else {
  alert("b is not an instance of A");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>

La raison pour laquelle b instanceof A est false est que instanceof vérifie les constructeurs: x instanceof A renvoie true si la fonction A est un constructeur quelque part dans la chaîne de prototypes de x (voir la documentation MDN sur instanceof ). Cependant, Lodash n'utilise pas de constructeur lorsqu'il clone des objets. Ça ne peut pas. (Comment pourrait-il savoir quels arguments passer?) Il crée un objet JS simple contenant toutes les méthodes de l'objet cloné, mais ne reproduisant pas sa chaîne de prototypes.

La variable clone de Lodash (et la plupart des méthodes de lodash, en réalité) est la mieux utilisée pour traiter des objets JS bruts. Si vous l'utilisez conjointement avec les constructeurs et instanceof, les choses deviennent un peu troubles. 


Une solution consiste à éviter la vérification instanceof et à faire quelque chose qui ressemble à la frappe à la machine. ne vérifiez pas que le constructeur de l'objet est une fonction particulière, mais vérifiez que l'objet possède les propriétés que vous attendez.

Comme suggéré dans les commentaires, une autre solution consiste à implémenter une méthode de clonage sur votre classe elle-même, qui n'utiliserait pas lodash.

class A() {
    clone() {
        var cloned = new A(); //pass appropriate constructor args
        //make other necessary changes to make the state match
        return cloned;
    }
}
7
Retsam

Il existe un article de blog intéressant sur le clonage profond http://blog.soulserv.net/understanding-object-cloning-in-javascript-part-ii/ . Vous pouvez utiliser la mise en oeuvre par l'auteur de la fonction de clonage en profondeur clone():

class SomeClass {
    constructor(public test) {

    }
}

let c = new SomeClass("Hey!");
c.test = "Hey!";

console.log(c instanceof SomeClass); // returns true

let cloneC = clone(c, /* resolve circular references */ true);

console.log(cloneC instanceof SomeClass); // returns true

[ clone () code source ] [ Code source complet ]

5
Martin Vseticka

Vous pouvez utiliser Lodash # cloneDeep utility. Exemple d'utilisation:

import * as _ from "lodash";

...

{
    this.cloned = _.cloneDeep(data);
}
1
Radouane ROUFID

Vous pouvez créer votre propre clone profond à l'aide des méthodes JSON stringify et parse:

export function deepClone<T>(obj: T): T {
  return JSON.parse(JSON.stringify(obj)) as T;
}
0
Julian Torregrosa