web-dev-qa-db-ja.com

コンストラクトシグネチャを持つインターフェイスはどのように機能しますか?

インターフェースでコンストラクターを定義する方法がうまくいかないのですが。私は何かを完全に誤解しているかもしれません。しかし、私はしばらくの間答えを探しましたが、これに関連するものを見つけることができません。

TypeScriptクラスに次のインターフェイスを実装するにはどうすればよいですか:

interface MyInterface {
    new ( ... ) : MyInterface;
}

Anders Hejlsbergは、これに似たものを含むインターフェイスを作成します video (約14分)。しかし、私の人生では、これをクラスに実装することはできません。

私はおそらく何かを誤解していますが、何が得られないのですか?

編集:

明確にするために。 「新しい(...)」では、「何でも」という意味でした。私の問題は、この基本的なバージョンでも動作しないことです:

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

これは私のためにコンパイルされていません。コンパイルしようとすると、「クラス「テスト」はインターフェイス「MyInterface」を宣言しますが、実装しません:タイプ「MyInterface」には構成署名が必要ですが、タイプ「テスト」には署名がありません」を取得します。

編集:

それで、フィードバックを与えられてこれをもう少し調べた後。

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () => test { return this; }
}

有効なTypeScriptではないため、問題は解決しません。コンストラクターの戻り値の型を定義することはできません。 「テスト」を返します。次の署名:class test {constructor(){}}「new()=> test」のようです(オンラインエディタで「class」の上にカーソルを置くだけで取得できます)。そして、これは私たちが望むものであり、私はそれがそうなると思ったものです。

誰でもこれの例を提供したり、実際にコンパイルしている場所で同様のものを提供したりできますか?

編集(もう一度...):

だから、なぜインターフェイスでこれを定義することはできるが、TypeScriptクラスで実装することはできないのかというアイデアを思いついたかもしれません:

var MyClass = (function () {
    function MyClass() { }
    return MyClass;
})();

interface MyInterface {
    new () : MyInterface;
}

var testFunction = (foo: MyInterface) : void =>  { }
var bar = new MyClass();
testFunction(bar);

だから、これはjavascriptをインターフェースできるTypeScriptの唯一の機能ですか?または、javascriptを使用してクラスを実装することなくTypeScriptで実装することは可能ですか?

128
Nypan

インターフェースの構成署名は、クラスに実装できません。それらは、「新しい」機能を定義する既存のJS APIを定義するためだけのものです。動作するインターフェイスnew署名を含む例は次のとおりです。

interface ComesFromString {
    name: string;
}

interface StringConstructable {
    new(n: string): ComesFromString;
}

class MadeFromString implements ComesFromString {
    constructor (public name: string) {
        console.log('ctor invoked');
    }
}

function makeObj(n: StringConstructable) {
    return new n('hello!');
}

console.log(makeObj(MadeFromString).name);

これにより、次を使用してmakeObjを呼び出すことができるものに対する実際の制約が作成されます。

class Other implements ComesFromString {
    constructor (public name: string, count: number) {
    }
}

makeObj(Other); // Error! Other's constructor doesn't match StringConstructable
135
Ryan Cavanaugh

まったく同じ質問を探して、TypeScript-Teamがそれをどのように行ったかを調べました...

インターフェイスを宣言し、その後、インターフェイス名と完全に一致する名前を持つ変数を宣言しています。これは、静的関数を入力する方法でもあります。

lib.d.tsの例:

interface Object {
    toString(): string;
    toLocaleString(): string;
    // ... rest ...
}
declare var Object: {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    // ... rest ...
}

私はそれを試してみましたが、それは魅力のように機能します。

54
Nils

コンストラクトシグネチャを持つインターフェイスは、どのクラスでも実装されることを意図していません(一見、これは私のようなC#/ Javaのバックグラウンドを持つ人にとっては奇妙に見えるかもしれませんが、チャンスを与えてください)。少し違います。

しばらくの間、それを呼び出し署名(Java worldの@FunctionalInterfaceのような)を持つインターフェースと考えてください。その目的は、関数の種類を記述することです。記述された署名は、関数オブジェクトによって満たされることになっています...しかし、高レベルの関数やメソッドだけではありません。オブジェクトの作成方法を知っている関数、newキーワードが使用されたときに呼び出される関数でなければなりません。

したがって、構成署名を持つインターフェースは、コンストラクターの署名を定義します!インターフェイスで定義されたシグネチャに準拠する必要があるクラスのコンストラクター(コンストラクターがインターフェイスを実装すると考えてください)。それは、ビルダーまたは工場のようなものです!

最も一般的な使用方法を示すための短いコードスニペットを次に示します。

interface ClassicInterface { // old school interface like in C#/Java
    method1();
    ...
    methodN();
}

interface Builder { //knows how to construct an object
    // NOTE: pay attention to the return type
    new (myNumberParam: number, myStringParam: string): ClassicInterface
}

class MyImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Builder
    constructor(num: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

class MyOtherImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Builder
    constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

// And here is the polymorphism of construction
function instantiateClassicInterface(ctor: Builder, myNumberParam: number, myStringParam: string): ClassicInterface {
    return new ctor(myNumberParam, myStringParam);
}

// And this is how we do it
let iWantTheFirstImpl = instantiateClassicInterface(MyImplementation, 3.14, "smile");
let iWantTheSecondImpl = instantiateClassicInterface(MyOtherImplementation, 42, "vafli");
4
egelev

設計の観点から、インターフェイスでコンストラクターの要件を指定することは通常ありません。インターフェイスは、オブジェクトに対して実行できる操作を記述する必要があります。インターフェイスを実装するさまざまなクラスは、必要に応じて異なるコンストラクターパラメーターを要求できるようにする必要があります。

たとえば、インターフェイスがある場合:

interface ISimplePersistence {
    load(id: number) : string;
    save(id: number, data: string): void;
}

コンストラクターパラメーターを必要としないCookieとしてデータを保存するための実装と、コンストラクターパラメーターとして接続文字列を必要とするデータベースにデータを保存するバージョンがあります。

それでもインターフェイスでコンストラクタを定義したい場合は、これを行うための汚い方法があります。私はこの質問に答えるために使用していました。

型チェックではなく構成署名を持つインターフェース

3
Fenton

意図された動作を実現するために、 Decorators を使用できます。

この

interface MyInterface {
    new ();
}

function MyInterfaceDecorator(constructor: MyInterface) {
}


@MyInterfaceDecorator
class TestClass {
    constructor () { }
}

問題なくコンパイルします。対照的に、TestClassの次の定義

// error TS2345: Argument of type 'typeof TestClass' is not assignable to parameter of type 'MyInterface'.
@MyInterfaceDecorator
class TestClass {
    constructor (arg: string) { }
}

コンパイルしません。

2
MarvinDV