web-dev-qa-db-ja.com

TypeScriptでの匿名/インラインインターフェイスの実装

TypeScriptを始めたばかりで、次のインラインオブジェクト定義が有効と見なされない理由を理解しようとしています。私はオブジェクトのコレクションを持っています-それらの型は(私にとって)無関係ですが、それらを反復処理するときにコレクションの各オブジェクトにインターフェイスメソッドが存在することがわかります。

必要なメソッドを実装するために必要な個人情報を含むオブジェクトを作成しようとしたときに、「コンパイラ」エラーが発生しました。

_interface Doable {
    do();
}

function doThatThing (doableThing: Doable) {
    doableThing.do();
}

doThatThing({
    private message: 'ahoy-hoy!', // compiler error here
    do: () => {
        alert(this.message);
    }
});
_

コンパイラエラーメッセージは "タイプ'{ message: string, do: () => void; }'の引数はタイプDoableに割り当てられません。オブジェクトリテラルは既知のプロパティを指定する必要があり、タイプ 'Doableに' message 'は存在しません」 。関数呼び出しの外部でオブジェクトを定義する場合、同じメッセージが表示されることに注意してください。

_var thing: Doable;
thing = {
    private message: 'ahoy-hoy!', // error here
    do: () => {
        alert(this.message);
    }
};
doThatThing(thing);
_

「予期しない」メソッドも追加すると、同じエラーが発生します。

_doThatThing({
    do: () => {
        alert("ahoy hoy");
    },
    doSecretly: () => { // compiler error here now
        alert("hi there");
    }
});
_

JavaScriptを見て、インラインオブジェクト定義内のthisがグローバルオブジェクトにスコープされていることを発見しました。

_var _this = this; // wait, no, why!?
function doThatThing(doableThing) {
    doableThing.do();
}
doThatThing({
    message: 'ahoy-hoy!',
    do: function () {
        alert(_this.message); // uses global 
    }
});
_

TypeScriptのインターフェイスのインライン実装に関する情報を検索しようとしましたが、この問題に特化したものを見つけることができませんでした。

「修正された」コンパイル済みJSが意図したとおりに機能することを確認できます。

_function doThatThing(doableThing) {
    doableThing.do();
}

doThatThing({
    message: 'ahoy-hoy!',
    do: function () {
        alert(this.message);
    }
});
_

...そして、それは私にとって理にかなっています。なぜなら、これは暗黙的にObjectコンストラクターを呼び出しているからです。したがって、thisは新しいObjectインスタンスにスコープされる必要があります。

唯一の解決策は、各実装をインターフェースを実装するクラスとして宣言することのようですが、各クラスのインスタンスは1つだけにするので、それは本当に退屈で重いです。呼び出された関数との唯一のコントラクトがインターフェイスの実装である場合、オブジェクトに追加のメンバーを含めることができないのはなぜですか?

申し訳ありませんが、これは私が意図したよりも長くなりました...要約すると、私は尋ねています:

  1. インラインインターフェイスの実装(「 Java 」と言われる「匿名クラス」)がTypeScriptで無効と見なされるのはなぜですか?具体的には、そのコンパイラエラーは何を意味し、何に対して保護しますか?
  2. グローバルオブジェクトへのスコープの再割り当てが「コンパイル済み」JavaScriptで生成されるのはなぜですか?
  3. それが私のエラーであると仮定すると(たとえば、いくつかの望ましくない状態から保護するためにコンパイラエラーが必要であると)、本当にそう事前にクラスを明示的に宣言する唯一の解決策ですか?
_interface Doable {
    do() : void;
}

class DoableThingA implements Doable { // would prefer to avoid this ...
    private message: string = 'ahoy-hoy';
    do() {
        alert(this.message);
    }
}

class DoableThingB implements Doable { // ... as well as this, since there will be only one instance of each
    do() {
        document.getElementById("example").innerHTML = 'whatever';
    }
}

function doThatThing (doableThing: Doable) {
    doableThing.do();
}

var things: Array<Doable>;
things = new Array<Doable>();
things.Push(new DoableThingA());
things.Push(new DoableThingB());

for (var i = 0; i < things.length; i++) {
    doThatThing(things[i]);
}
_

追伸コンパイルされたJSのスコープのバグが1.6と1.5の両方で発生していますが、コンパイラエラーは今日TS 1.6にアップグレードしたときにのみ現れました。

更新:FrançoisCardinauxは この回答 へのリンクを提供しました。これは型アサーションの使用を推奨しますが、これはコンパイラエラーのみを削除し、実際に不適切なスコープによる論理エラーを引き起こします

_interface Doable {
    do();
}

function doThatThing (doableThing: Doable) {
    doableThing.do();
}

doThatThing(<Doable>{ // assert that this object is a Doable
    private message: 'ahoy-hoy!', // no more compiler error here
    do: () => {
        alert(this.message);
    }
});
_

コンパイルされたJSを見ると、これは正しくありません。

_var _this = this; // very wrong, and now hidden
function doThatThing(doableThing) {
    doableThing.do();
}
doThatThing({
    message: 'ahoy-hoy!',
    do: function () {
        alert(_this.message); // s/b "this.message", which works in JS (try it)
    }
});
_
33
jezzer

OK、ついに質問2の問題を発見しました-太い矢印を使用していました=>ここでオブジェクトのメソッドを宣言します。

doThatThing(<Doable>{ 
    private message: 'ahoy-hoy!', 
    do: () => { // using fat arrow: global scope replaces new object's scope
        alert(this.message);
    }
});

...グローバルスコープをメソッドに「吸い込み」ます。この問題は、次のような長い構文を使用して修正されます。

doThatThing(<Doable>{
    private message: 'ahoy-hoy!',
    do: function() { // using "regular" anonymous function syntax, "this" meaning is preserved
        alert(this.message);
    }
});

要約すると:

  1. 未回答;
  2. コードにタイプミスがあり、「=>」の代わりに「function()」を使用していたはずです。そして、
  3. インターフェイスを使用してオブジェクトを型アサートすると、コンパイラエラーが削除されます。
15
jezzer