読者です 読者をやめる 読者になる 読者になる

Kokudoriing

技術系与太話ブログ

call signatureを実装したインターフェイスを実装したクラスを作りたかった

TypeScript

何を言ってるのかわけわかんないですね。
TypeScriptのクラスはJavaScriptにコンパイルされると関数になります。
なので、直接呼び出すことも、new付きで呼び出すことも(JavaScript的には)可能です。

ただ、クラスがnew付きで呼び出し可能であることは直感的ですが、
クラスがそのまま関数呼び出しできることは直感的じゃないわけで。
(この複雑性の問題は、JavaScriptにクラスがなく、TypeScriptにクラスがあることに起因しています。)

さて、TypeScriptにはインターフェイスがあり、関数呼び出しシグネチャとnew付き呼び出しシグネチャの両方がサポートされてます。

interface Hoge {
	(age :number);
	new(name :string);
}

上記のHogeインターフェイスの場合だと、関数呼び出しの場合はnumber型のageという引数を1つ要求しています。
しかし、new付き呼び出しの場合だとstring型のnameという引数を1つ要求しています。

ここまでは素敵ですね。問題はクラスです。

class Piyo implements Hoge {
	(age :number) {
		// コンパイルエラー
	}
	new(name :string) {
		// 問題なし
	}
}

早い話が、クラスで関数呼び出しのシグニチャ(call signature)がサポートされてないっぽいです。
シグニチャ表現が違うだけとかいうオチな気がしなくも無いですが‥。
一応ドキュメントは読んだのですが、英語力が大変アレなのでご存知の方いらっしゃいましたらご教授頂ければ幸いです。

もちろんこれが関数呼び出しだけなら関数にすれば良い話なのですが、
スタティックプロパティやインスタンスプロパティを入れたいとなるとどうしよう。


というわけでクラスを使わないJavaScriptっぽい解決策。

interface HogeStatic {
	(age :number) :HogeInstance;
	foo() :void;
}

interface HogeInstance {
	bar() :void;
}

var Piyo :HogeStatic = (function () {
	var Tmp: any = function (age :number) {
		if (!(this instanceof Tmp))
			return new Tmp(age)
	}
	Tmp.foo = function () {
		console.log('foo')
	}
	Tmp.prototype.bar = function() {
		console.log('bar')
	}
	return Tmp;
})();

Piyo.foo() // output 'foo'
Piyo(100).bar() // output 'bar'

内部関数Tmpは明示的にany型と注釈しないとTmp.foo = ...がコンパイルエラーになってしまいます。

ただこれ結局newだろうが関数呼び出しだろうが同じ結果になるんですよね。
ただ、newと関数呼び出しを区別したいかと言われると微妙。
(というか使用側としてはそこを気にしたくない…。)

というわけでクラスのnew() {}をnewと関数呼び出しの両方のシグニチャみたいに扱って欲しいなーと。
ただ最初に述べた通り罠っぽくはある。
そしてジェネリクス欲しい。
1.0までには入るみたいですね、ジェネリクス