Typescript(各種シグネチャ)
はじめに
以前の記事でインデックスシグネチャについての記事を書きました。
Typescript(インデックスシグネチャ) - 駆け足エンジニアの記録
今回はその他のシグネチャについての記事になります。ちなみに、「シグネチャ」の意味ですが、関数の名前、引数の数や型、返り値の型等のデータ構造のインターフェースといったところです。
関数シグネチャ(コールシグネチャ)
インターフェースはオブジェクトの型指定のみ可能ですが、例外として関数型を定義できます。
//インターフェースによる関数型(関数シグネチャ) interface AddFn { (a: number, b: number): number; } const add: AddFn = (n1, n2) => { return n1 + n2; }; console.log(add(2, 4)); //6
これが関数シグネチャの基本です。これでは味気ないので、通常の関数オーバーロードを関数シグネチャで書き換えてみましょう。
関数オーバーロードについても以前記事にしましたので参考にしてください。
Typescript(関数オーバーロード) - 駆け足エンジニアの記録
//通常の関数オーバーロード type Combinable = number | string; function combine(input1: number, input2: number): number; function combine(input1: number, input2: string): string; function combine(input1: string, input2: number): string; function combine(input1: string, input2: string): string; function combine(input1: Combinable, input2: Combinable) { if (typeof input1 === "string" || typeof input2 === "string") { return input1.toString() + input2.toString(); } return input1 + input2; } const result1 = combine(1, 2); console.log(result1.toFixed(2)); //3.00 const result2 = combine("ROUND", 1); console.log(result2.split("")); //(6) ["R", "O", "U", "N", "D", "1"]
これを関数シグネチャを用いて書き換えると、
//関数シグネチャの利用 type Combinable = number | string; interface Combine { (input1: number, input2: number): number; (input1: number, input2: string): string; (input1: string, input2: number): string; (input1: string, input2: string): string; } const combine: Combine = (input1: any, input2: Combinable) => { if (typeof input1 === "string" || typeof input2 === "string") { return input1.toString() + input2.toString(); } return input1 + input2; }; const result1 = combine(1, 2); console.log(result1.toFixed(2)); //3.00 const result2 = combine("ROUND", 1); console.log(result2.split("")); //(6) ["R", "O", "U", "N", "D", "1"]
以上のようにCombineというインターフェースに関数シグネチャをまとめています。問題なく実装しますが、combineという関数式の引数の型をどちらもCombinable(input1: Combinable, input2: Combinable)とするとエラーになりました。このエラーの理由が分かる方コメント欄で教えて下さい。宜しくお願いします。
メソッドシグネチャ
関数シグネチャとよく似た機能で、クラスやオブジェクトにあるメソッドを持たせることを強制させるために利用します。
//メソッドシグネチャ interface Hello { hello(): void; } class Person implements Hello {//クラスにメソッドシグネチャを実装 private gender: string = "male"; constructor(private name: string, private age: number) {} hello() { console.log(`hello ${this.name}`); } } const bob = new Person("Bob", 22); //生成されるインスタンスはオブジェクト bob.hello(); //hello Green
プロパティシグネチャ
メソッドシグネチャ同様、クラスやオブジェクトにあるプロパティを持たせることを強制させるために利用します。
ただしクラスにプロパティシグネチャを実装する際、以下のコードはエラーを出します。
//プロパティシグネチャ&メソッドシグネチャ interface Hello { name: string; age: number; hello(): void; } class Person implements Hello { private gender: string = "male"; constructor(private name: string, private age: number) {} hello() { console.log(`hello ${this.name}`); } } const bob = new Person("Bob", 22); //生成されるインスタンスはオブジェクト
このエラーの原因はnameプロパティの修飾子に矛盾が生じているためです。具体的に説明すると、コンストラクター関数の引数内、nameプロパティにprivateというアクセス修飾子がついています。しかしプロパティシグネチャではnameプロパティはデフォルトでpublicというアクセス修飾子であるため修飾子が異なります。
インターフェースはClassと異なり、TSでのみ存在する仮想構造です。TSコンパイラーが型チェックのためだけに利用するので、コンパイル後には残らず、もちろん利用されることはありません。Interfaceはただただオブジェクトが持つべきプロパティの名前と型を約束するだけのもの(修飾子を付けることはできません)だからです。
このエラーを無くすための一つの方法はメソッドシグネチャとプロパティシグネチャを分けて、コンストラクター関数の引数にプリミティブ型ではなく、オブジェクト型を渡すことです。
//メソッドシグネチャ interface Hello { hello(): void; } //プロパティシグネチャ interface Options { name: string; age: number; } class Person implements Hello { private gender: string = "male"; constructor(private options: Options) {} hello() { console.log(`hello ${this.options.name}`); } } const bob = new Person({ name: "Bob", age: 22 }); //生成されるインスタンスはオブジェクト bob.hello(); //hello Green
newシグネチャ
オブジェクト型にnew演算子が使えることを定義します。あまり利用価値はないのかなと思いますが...
interface Hello { new (name: string, age: number): Person; } class Person { private gender: string = "male"; constructor(private name: string, public age: number) {} hello() { console.log(`hello ${this.name}`); } } const bobsan: Hello = Person; //生成されるインスタンスはオブジェクト const bob: Person = new bobsan("Bob", 22); bob.hello(); //hello Green
以上、各種シグネチャとなります。