web-dev-qa-db-ja.com

クラス引数を使用したジェネリック型推論

渡した型に基づいてジェネリック型を定義するという問題があります。

クラスを「アクティブ化」するコードがありますが、typeパラメーターから型情報を取得できないため、(インスタンスではなく)クラスオブジェクトを渡します。ただし、これは型推論を破ります。

これが私がやろうとしていることの簡単な例です:

interface IActivatable {
    id: number;
    name:string;
}

class ClassA implements IActivatable {
    public id: number;
    public name: string;
    public address:string;
}

class ClassB implements IActivatable {
    public id: number;
    public name: string;
    public age: number;
}

function activator<T extends IActivatable>(type:T): T {
    // do stuff to return new instance of T.
}

var classA:ClassA = activator(ClassA);

これまでのところ、私が思いついた唯一の解決策は、type引数の型をanyに変更し、ジェネリック型も手動で設定することです(以下を参照)。しかし、これは長蛇の列のようですが、これを達成する別の方法はありますか。

function activator<T extends IActivatable>(type:any): T {
    // do stuff to return new instance of T.
}

var classA:ClassA = activator<ClassA>(ClassA);

あなたが与えることができるどんな助けにも感謝します。

14
Antony Jones

TypeScriptの型情報はコンパイル中にすべて消去されるため、たとえば実行時に、汎用型を直接使用することはできません。

だからここにあなたができることがあります...

名前を文字列として渡すことにより、名前でクラスを作成できます。はい;これには、マジックストリングの杖を振ることが含まれます。また、名前に影響を与える可能性のあるツールチェーン内のすべてに注意する必要があります。たとえば、名前をクラッシュさせるミニファイア(マジックストリングが同期しなくなる)などです。

class InstanceLoader<T> {
    constructor(private context: Object) {

    }

    getInstance(name: string, ...args: any[]) : T {
        var instance = Object.create(this.context[name].prototype);
        instance.constructor.apply(instance, args);
        return <T> instance;
    }
}

var loader = new InstanceLoader<IActivatable>(window);

var example = loader.getInstance('ClassA');

実行時にインスタンスから型名を取得することもできます。これは、以下の例の形式で示しています 実行時にTypeScriptクラス名を取得する

class Describer {
    static getName(inputClass) { 
        var funcNameRegex = /function (.{1,})\(/;
        var results = (funcNameRegex).exec((<any> inputClass).constructor.toString());
        return (results && results.length > 1) ? results[1] : "";
    }
}

class Example {
}

class AnotherClass extends Example {
}

var x = new Example();
var y = new AnotherClass();

alert(Describer.getName(x)); // Example
alert(Describer.getName(y)); // AnotherClass

これは、タイプ名を取得してからオブジェクトcreate stuffを使用して別のものを取得できるため、「同じ種類の別の」を生成する場合にのみ関係します。

4
Fenton

言語仕様によると、コンストラクター関数でクラス型を参照する必要があります。したがって、_type:T_を使用する代わりに、次のようにtype: { new(): T;}を使用します。

_function activator<T extends IActivatable>(type: { new(): T ;} ): T {
    // do stuff to return new instance of T.
    return new type();
}

var classA: ClassA = activator(ClassA);
_
43
blorkfish

TypeScriptチームが推奨する方法は次のとおりです。
基本的にblorkfishの回答と同じですが、インターフェースに抽出されます。

interface IConstructor<T> {
    new (...args: any[]): T;

    // Or enforce default constructor
    // new (): T;
}

interface IActivatable {
    id: number;
    name: string;
}

class ClassA implements IActivatable {
    public id: number;
    public name: string;
    public address: string;
}

class ClassB implements IActivatable {
    public id: number;
    public name: string;
    public age: number;
}

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

const classA = activator(ClassA);

ソース

9
Red Riding Hood

あなたの問題は、ClassAが実際にはタイプTではなくIActivatableを拡張することです。実際、コンパイルされたjavascriptのClassAは、実際にはコンストラクター関数を返す関数です。

0
Peter