TypeScriptの型の互換性について
今回はTypeScriptの型の互換性について取り上げます。
型の互換性・・・??
簡単に言うと例えば、ある型の変数であるHogeに、別の型であるFugaが代入できれば、型の互換性があると言えます。逆に代入できなければ型の互換性がないとなります。
この型の互換性がどのように決まるかは言語によって異なります。TypeScriptの場合は、構造的部分型(Structural Subtyping)が採用されています。
言葉だけで捉えるとなんだか難しそうですね・・・ 実際にコードを見て確認します。
interface Tree { age: number } class Person { age: number } const tanaka: Tree = new Person(); tanaka.age = 20; console.log(tanaka.age); // 20
上記のようにPersonクラスがTreeを実装していなくてもメンバーが同じなのでコンパイルエラーにはならないのです。 これを構造的部分型(Structural Subtyping)というのです。
一方でC#やJavaの場合は、Person
クラスがTree
インターフェースを実装していないといけないので上記のコードではエラーになります。(自分はC#やJavaを触ったことがないので別の機会でお試しください。。。)
このようなものを名目上の型付けと呼びます。
また以下の例はどうでしょうか?
interface User { name: string; } let x: User; let y = { name:"hoge", address: "Tokyo" } x = y;
こちらは正常にコンパイルされます。
変数y
が変数x
のプロパティ(ここでいうとname)をもっているので割り当てが可能になります。
逆にy
にx
を代入した時はどうでしょうか?
interface User { name: string; } let x: User; let y = { name:"hoge", address: "Tokyo" } y = x;
お察しかもしれませんが、y
に対応するプロパティをx
はname
しか持っていないのでコンパイルエラーになります。
関数の場合はどのように型を評価しているのでしょうか?
以下のような関数があります。
let hoge = (a: number) => 0; let fuga = (b: number, c: string) => 0; fuga = hoge;
結果としては、fugaにhogeを割り当て可能です。注意する点としては、引数の名前が異なっていても型のみが考慮されるのです。
hogeの関数の引数名はa
ですが、fugaの関数の引数名はb
とc
です。
上記の例でみたオブジェクトの例では、プロパティ名が異なっていると正しく割り当てされません。
またfuga = hoge
のように引数が切り捨てられるのが許される理由としては、関数の引数が無視されることがJavaScriptでよくみられるからだそうです。
例えば、以下の例です。
const names = ["田中", "佐藤", "中村"]; const result = names.some(name => name === "田中"); // true
someメソッドのコールバック関数のパラメーターは以下の3つを受け取ります。
- 配列の中の値
- 各要素のインデックス
- 要素を格納している配列
このパラメーターを全て使う時もあるとはありますが、使わないケースも多いです。このことから引数の切り捨てられるのが許されるようです。
まとめ
このようにTypeScriptには構造的部分型が取り入れられています。オブジェクトや関数などによって型の評価の仕方が異なるのです。 次回はジェネリクスのことについて取り上げたいと思います。