ジェネリッククラスの型パラメーターから新しいオブジェクトを作成する


144

ジェネリッククラスに型パラメーターの新しいオブジェクトを作成しようとしています。私のクラスViewでは、型パラメーターとして渡されるジェネリック型のオブジェクトの2つのリストがありますが、作成しようとするとnew TGridView()、TypeScriptは次のように言います。

シンボル 'TGridViewが見つかりませんでした

これはコードです:

module AppFW {
    // Represents a view
    export class View<TFormView extends FormView, TGridView extends GridView> {
        // The list of forms 
        public Forms: { [idForm: string]: TFormView; } = {};

        // The list of grids
        public Grids: { [idForm: string]: TGridView; } = {};

        public AddForm(formElement: HTMLFormElement, dataModel: any, submitFunction?: (e: SubmitFormViewEvent) => boolean): FormView {
            var newForm: TFormView = new TFormView(formElement, dataModel, submitFunction);
            this.Forms[formElement.id] = newForm;
            return newForm;
        }

        public AddGrid(element: HTMLDivElement, gridOptions: any): GridView {
            var newGrid: TGridView = new TGridView(element, gridOptions);
            this.Grids[element.id] = newGrid;
            return newGrid;
        }
    }
}

ジェネリック型からオブジェクトを作成できますか?

回答:


95

コンパイルされたJavaScriptはすべての型情報が消去されているため、使用できません Tオブジェクトの新規作成に。

型をコンストラクタに渡すことにより、一般的ではない方法でこれを行うことができます。

class TestOne {
    hi() {
        alert('Hi');
    }
}

class TestTwo {
    constructor(private testType) {

    }
    getNew() {
        return new this.testType();
    }
}

var test = new TestTwo(TestOne);

var example = test.getNew();
example.hi();

ジェネリックを使用してこの例を拡張し、タイプを強化することができます。

class TestBase {
    hi() {
        alert('Hi from base');
    }
}

class TestSub extends TestBase {
    hi() {
        alert('Hi from sub');
    }
}

class TestTwo<T extends TestBase> {
    constructor(private testType: new () => T) {
    }

    getNew() : T {
        return new this.testType();
    }
}

//var test = new TestTwo<TestBase>(TestBase);
var test = new TestTwo<TestSub>(TestSub);

var example = test.getNew();
example.hi();

144

ジェネリックコード内で新しいオブジェクトを作成するには、コンストラクター関数で型を参照する必要があります。だからこれを書く代わりに:

function activatorNotWorking<T extends IActivatable>(type: T): T {
    return new T(); // compile error could not find symbol T
}

これを書く必要があります:

function activator<T extends IActivatable>(type: { new(): T ;} ): T {
    return new type();
}

var classA: ClassA = activator(ClassA);

この質問を参照してください: クラス引数によるジェネリック型推論


35
やや遅い(しかし有用な)コメント-コンストラクターが引数を取る場合、new()ニーズも必要です-またはより一般的なもの:type: {new(...args : any[]): T ;}
Rycochet

1
@Yoda IActivatableが自己作成したインタフェースで、他の質問を参照してください。stackoverflow.com/questions/24677592/...
ジェイミー・

1
これをどのように推論すべきか{ new(): T ;}。この部分を学ぶのに苦労しています。
abitcode

1
これ(およびここでの他のすべてのソリューション)では、ジェネリックを指定する前にクラスを渡す必要があります。これはハックで冗長です。これは、基本クラスがClassA<T>あり、それを拡張する場合ClassB extends ClassA<MyClass>、渡す必要があるnew () => T = MyClassか、これらのソリューションが機能しないことを意味します。
AndrewBenjamin

@AndrewBenjaminそれではあなたの提案は何ですか?何かを始めようとはしていませんが(実際に学びたい)、それをハッキーで冗長なものと呼ぶと、共有していない別の方法を知っていると思います:)
ペリー

37

すべての型情報はJavaScript側で消去されるため、@ Sohneeの状態のようにTを新しくすることはできませんが、型付きパラメーターをコンストラクターに渡すことをお勧めします。

class A {
}

class B<T> {
    Prop: T;
    constructor(TCreator: { new (): T; }) {
        this.Prop = new TCreator();
    }
}

var test = new B<A>(A);

12
export abstract class formBase<T> extends baseClass {

  protected item = {} as T;
}

そのオブジェクトは任意のパラメーターを受け取ることができますが、T型はtypescript参照にすぎず、コンストラクターを介して作成することはできません。つまり、クラスオブジェクトは作成されません。


6
注意してください。これは機能する可能性がありますが、結果のオブジェクトはType Tにならないため、で定義されているゲッター/セッターTは機能しない可能性があります。
DanielOrmeño2017

@DanielOrmeñoは説明する気ですか?それはどういう意味ですか?私のコードはこのスニペットを使用して動作しているようです。ありがとう。
eestein、2017

JavaScriptはインタプリタ言語であり、ecmascriptにはまだ型またはクラスへの参照がありません。したがって、Tはtypescriptの参照にすぎません。
jales cardoso 2017

JavaScriptでさえ、これは間違った答えです。がある場合class Foo{ method() { return true; }、{}をTにキャストしてもこのメソッドは提供されません!
JCKödel

8

遅く知っていますが、@ TadasPaの答えは、

TCreator: new() => T

の代わりに

TCreator: { new (): T; }

結果は次のようになります

class A {
}

class B<T> {
    Prop: T;
    constructor(TCreator: new() => T) {
        this.Prop = new TCreator();
    }
}

var test = new B<A>(A);

4

基本クラス内からジェネリックをインスタンス化しようとしていました。ファクトリメソッドを呼び出すために具象型が必要なため、上記の例はどれもうまくいきませんでした。

これについてしばらく調査したところ、オンラインで解決策を見つけることができなかった後、私はこれが機能しているように見えることを発見しました。

 protected activeRow: T = {} as T;

ピース:

 activeRow: T = {} <-- activeRow now equals a new object...

...

 as T; <-- As the type I specified. 

すべて一緒に

 export abstract class GridRowEditDialogBase<T extends DataRow> extends DialogBase{ 
      protected activeRow: T = {} as T;
 }

つまり、実際のインスタンスが必要な場合は、次のように使用する必要があります。

export function getInstance<T extends Object>(type: (new (...args: any[]) => T), ...args: any[]): T {
      return new type(...args);
}


export class Foo {
  bar() {
    console.log("Hello World")
  }
}
getInstance(Foo).bar();

引数があれば使用できます。

export class Foo2 {
  constructor(public arg1: string, public arg2: number) {

  }

  bar() {
    console.log(this.arg1);
    console.log(this.arg2);
  }
}
getInstance(Foo, "Hello World", 2).bar();

2
JavaScriptでさえ、これは間違った答えです。クラスFoo {method(){がある場合はtrueを返します。}、Tに{}をキャストしても、このメソッドは使用できません。
JCKödel

メソッドがない場合は動作します。ただし、メソッドを持つオブジェクトがある場合は、先ほど追加したコードを使用する必要があります。
クリスチャンパツァー

2

これは、タイプ情報を保持するために行うことです。

class Helper {
   public static createRaw<T>(TCreator: { new (): T; }, data: any): T
   {
     return Object.assign(new TCreator(), data);
   }
   public static create<T>(TCreator: { new (): T; }, data: T): T
   {
      return this.createRaw(TCreator, data);
   }
}

...

it('create helper', () => {
    class A {
        public data: string;
    }
    class B {
        public data: string;
        public getData(): string {
            return this.data;
        }
    }
    var str = "foobar";

    var a1 = Helper.create<A>(A, {data: str});
    expect(a1 instanceof A).toBeTruthy();
    expect(a1.data).toBe(str);

    var a2 = Helper.create(A, {data: str});
    expect(a2 instanceof A).toBeTruthy();
    expect(a2.data).toBe(str);

    var b1 = Helper.createRaw(B, {data: str});
    expect(b1 instanceof B).toBeTruthy();
    expect(b1.data).toBe(str);
    expect(b1.getData()).toBe(str);

});

1

私はこれを使用します:let instance = <T>{}; EDIT 1で一般的に機能します。

export class EntityCollection<T extends { id: number }>{
  mutable: EditableEntity<T>[] = [];
  immutable: T[] = [];
  edit(index: number) {
    this.mutable[index].entity = Object.assign(<T>{}, this.immutable[index]);
  }
}

5
これは実際にはの新しいインスタンスをインスタンス化するのではなくT、TypeScript がtypeであると考える空のオブジェクトを作成するだけTです。あなたの答えをもう少し詳しく説明したいと思うかもしれません。
Sandy Gifford 2017年

私はそれが一般的にうまくいくと言いました。それはあなたが言うとおりです。コンパイラは愚痴ではなく、一般的なコードがあります。このインスタンスを使用して、明示的なコンストラクターを必要とせずに、必要なプロパティを手動で設定できます。
Bojo 2017年

1

質問には完全には答えていませんが、これらの種類の問題のための素晴らしいライブラリがあります:https : //github.com/typestack/class-transformer彼らが本当に存在していないとして、それは、ジェネリック型では動作しませんが(実行時(すべての作業はクラス名(クラスコンストラクター)で行われます))

例えば:

import {Type, plainToClass, deserialize} from "class-transformer";

export class Foo
{
    @Type(Bar)
    public nestedClass: Bar;

    public someVar: string;

    public someMethod(): string
    {
        return this.nestedClass.someVar + this.someVar;
    }
}

export class Bar
{
    public someVar: string;
}

const json = '{"someVar": "a", "nestedClass": {"someVar": "B"}}';
const optionA = plainToClass(Foo, JSON.parse(json));
const optionB = deserialize(Foo, json);

optionA.someMethod(); // works
optionB.someMethod(); // works

1

私はパーティーに遅れましたが、これは私がそれを機能させる方法です。配列については、いくつかのトリックを行う必要があります。

   public clone<T>(sourceObj: T): T {
      var cloneObj: T = {} as T;
      for (var key in sourceObj) {
         if (sourceObj[key] instanceof Array) {
            if (sourceObj[key]) {
               // create an empty value first
               let str: string = '{"' + key + '" : ""}';
               Object.assign(cloneObj, JSON.parse(str))
               // update with the real value
               cloneObj[key] = sourceObj[key];
            } else {
               Object.assign(cloneObj, [])
            }
         } else if (typeof sourceObj[key] === "object") {
            cloneObj[key] = this.clone(sourceObj[key]);
         } else {
            if (cloneObj.hasOwnProperty(key)) {
               cloneObj[key] = sourceObj[key];
            } else { // insert the property
               // need create a JSON to use the 'key' as its value
               let str: string = '{"' + key + '" : "' + sourceObj[key] + '"}';
               // insert the new field
               Object.assign(cloneObj, JSON.parse(str))
            }
         }
      }
      return cloneObj;
   }

次のように使用します。

  let newObj: SomeClass = clone<SomeClass>(someClassObj);

それは改善することができますが、私のニーズのために働きました!


1

これを直接追加するのは、質問を直接解決すると思うからではありません。私のソリューションには、SQLデータベースのテーブルを表示するためのテーブルコンポーネントが含まれます。

export class TableComponent<T> {

    public Data: T[] = [];

    public constructor(
        protected type: new (value: Partial<T>) => T
    ) { }

    protected insertRow(value: Partial<T>): void {
        let row: T = new this.type(value);
        this.Data.push(row);
    }
}

これを使用するには、データベースVW_MyDataにビュー(またはテーブル)があり、クエリから返されたすべてのエントリに対してVW_MyDataクラスのコンストラクターをヒットする必要があると仮定します。

export class MyDataComponent extends TableComponent<VW_MyData> {

    public constructor(protected service: DataService) {
        super(VW_MyData);
        this.query();
    }

    protected query(): void {
        this.service.post(...).subscribe((json: VW_MyData[]) => {
            for (let item of json) {
                this.insertRow(item);
            }
        }
    }
}

これがDataに戻り値を単に割り当てるよりも望ましい理由は、コンストラクターでVW_MyDataのいくつかの列に変換を適用するコードがあることです。

export class VW_MyData {
    
    public RawColumn: string;
    public TransformedColumn: string;


    public constructor(init?: Partial<VW_MyData>) {
        Object.assign(this, init);
        this.TransformedColumn = this.transform(this.RawColumn);
    }

    protected transform(input: string): string {
        return `Transformation of ${input}!`;
    }
}

これにより、TypeScriptに送信されるすべてのデータに対して、変換、検証などを実行できます。うまくいけば、それは誰かにいくつかの洞察を提供します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.