web-dev-qa-db-ja.com

TypeScriptのディープクローン(型を保持)

TypeScriptでオブジェクトをディープクローンする必要があります。 Lodashのようなライブラリーが適切な機能を提供するため、これは問題になりません。ただし、これらは型情報を破棄するようです。

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

この入力情報を保持しながら、TypeScriptでオブジェクトを複製する方法はありますか?

12
Julian B

TypeScriptはここで型情報を破棄しません。 DefinitelyTyped lodash.d.ts ファイルでは、cloneDeepが次のように定義されていることがわかります。

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

気にしない引数は無視して、入力としてTを受け取り、出力としてTを吐き出します。したがって、TypeScriptが型情報を失うことはありません。 cloneDeepの出力は入力と同じタイプであると見なされます。

エディターでこれを確認できるはずです。変数のタイプやオートコンプリートメソッドを検査できるエディターがあると仮定すると(そうでない場合は強くお勧めします)。


なぜtypeofが期待どおりに機能しないのですか?これは、TypeScript型情報がランタイムに引き継がれないためです。 instanceofはネイティブのJS演算子であり、TypeScriptは動作を変更しません。次のスニペットを実行することで確認できます。

"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>

b instanceof Aがfalseの場合、instanceofはコンストラクターに対してチェックを行います:x instanceof Aは、関数Aがxのプロトタイプチェーンのどこかのコンストラクターである場合にtrueを返します( instanceof のMDNドキュメントを参照)。ただし、Lodashはオブジェクトを複製するときにコンストラクターを使用しません。できません。 (どの引数を渡すかをどのように知るのでしょうか?)クローンオブジェクトのすべてのメソッドを含むプレーンなJSオブジェクトを作成しますが、プロトタイプチェーンを再現しません。

Lodashのclone(および実際にはほとんどのlodashのメソッド)は、生のJSオブジェクトを扱う場合に最適に使用されます。コンストラクターとinstanceofを組み合わせて使用​​している場合、物事の確認は少し曖昧になります。


ここでの解決策の1つは、instanceofチェックを回避し、ダックタイピングに似た何かをすることです。オブジェクトのコンストラクターが特定の関数であることを確認するのではなく、オブジェクトが期待するプロパティを持っていることを確認してください。

別の解決策は、コメントで示唆されているように、lodashを使用しないクラス自体にcloneメソッドを実装することです。

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

ディープクローニングに関する興味深いブログ投稿があります http://blog.soulserv.net/understanding-object-cloning-in-javascript-part-ii/ 。ディープクローニング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()ソースコード ] [ 完全なソースコード ]

5
Martin Vseticka

Lodash#cloneDeep ユーティリティを使用できます。使用例:

import * as _ from "lodash";

...

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

JSON stringifyおよび解析メソッドを使用して、独自のディープクローンを作成できます。

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