TypeScriptによる静的型付け言語入門① 『部分型』
こんにちは!
スマレジ テックファームのMichiです!
6月はTypeScriptキャッチアップ月間として、学んだことを記事にしていきます。
今回はその中でも、考え方にドハマりした『部分型』の考え方について、復習がてら分かりやすく解説します。
部分型とは?
まず、部分型についてざっくり解説します。
部分型とは、ある型が別の型の特性を含んでいるときの関係を表す概念です。もし型Sが型Tの部分型であるならば、型Sは型Tとしても扱うことができます。
これだけ書いても何も分からないと思うので、例を出して説明します。
次のように、生徒の情報を記録するオブジェクトの型: Student
が存在するとします。
type Student = { name: string; height: number; };
このStudent
型は、名前と身長を記録するプロパティ: name
とheight
を持ちます。
次に、このStudent
型に生徒の誕生日の情報も追加したいという要望があったので、新たにDetailedStudent
型も作成しました。
type DetailedStudent = { name: string; height: number; birthday: Date; };
これは見ての通り、先ほどのStudent
型にbirthday
プロパティを追加した型です。
このとき、DetailedStudent
型はStudent
型の部分型であるということができます。
最初に述べた通り、型Sが型Tの部分型であるとき、型Sは型Tとしても扱うことができます。よって、DetailedStudent
型はStudent
型の代わりとして使うことができます。
const john: DetailedStudent = { name: "John", height: 170, birthday: new Date('2005-12-17'), }; const student: Student = john; // DetailedStudentはStudentの部分型なので、この代入は可能。 console.log(student.name); // "John" console.log(student.height); // 170
なぜこれが可能なのかというと、DetailedStudent
型はStudent
型のプロパティをすべて持っているからです。
Student
型のプロパティはname
とheight
の2つだけです。そして、DetailedStudent
型はこれらのプロパティを2つとも持っています。
このとき、「あれ、Student
型はbirthday
を持っていないけど、それはいいの?」と思われる方もいるかもしれません。これを考えるために、Student
型の定義についてもう一度見直してみましょう。
Student
は『name
プロパティがstring
型で、かつheight
プロパティがnumber
型であるオブジェクト』の型です。
ここで注目してほしいのは、name
とheight
を以外のプロパティには言及していないということです。実は、name
とheight
以外のプロパティは持っていても持っていなくても構わないのです。
よって、DetailedStudent
型はStudent
型の代わりに使うとこができる、要するに、DetailedStudent
型はStudent
型の部分型であるということができます。
反対に、Student
型のオブジェクトをDetailedStudent
型の変数に代入しようとすると、TypeScriptはエラーを発生させます。それはStudent
型がDetailedStudent
型の全てのプロパティ(この場合はbirthday
)を持っていないためです。
const lisa: Student = { name: "Lisa", height: 160 }; const detailedStudent: DetailedStudent = lisa; // これはエラーになる
わかりやすいように、イメージ図を作ってみました。
DetailedStudent
型はStudent
型に含まれているので、DetailedStudent
型はStudent
型であると言い換えることができますね。オブジェクト指向の継承の概念によく似ています。
余談:なぜStudent
型のbirthday
プロパティにアクセスできないのか
先ほど、「Student
型はname
とheight
以外のプロパティは持っていても持っていなくても構わない」と述べました。そこで、TypeScriptを少し触ったことのある方ならこう思うでしょう。
「name
とheight
以外のプロパティにアクセスしようとするとエラーになるけど、矛盾してない?」
実際に、次のコードはエラーとなります。
type Student = { name: string; height: number; }; const mike: Student = {name: "Mike", height: 178} console.log(mike.birthday) // エラー: Property 'birthday' does not exist on type 'Student'.
Student
型はbirthday
プロパティを持っているかもしれないのに、実際はTyepScriptによって、birthday
へアクセスすることが防がれます。
これは、持っているか持っていないか分からないプロパティに触るのは危険でありミスの可能性が高いので、TypeScriptのおっせかいで、明示的に宣言しているname
とheight
以外のプロパティにアクセスするのはコンパイルエラーによって防がれているのです。
また、同じような理由から次のように、存在するか分からないプロパティに代入を行おうとするコードもエラーとなります。
type Student = { name: string; height: number; }; const mike: Student = {name: "Mike", height: 178, birthday: new Date('2001-05-09'),} // エラー: Type '{ name: string; height: number; birthday: Date; }' is not assignable to type 'Student'. // Object literal may only specify known properties, and 'birthday' does not exist in type 'Student'.
一個前のコードのように、存在するかどうか分からないプロパティ: birthday
にアクセスすることは許されません。
どうせアクセスできないのなら、代入しても使い道がないので、まったく無意味なプログラムです。無意味なプログラムはミスである可能性が高いですから、TypeScriptがおっせかいをして、このようなエラーを出してくれているのです。
まとめ
TypeScriptの記事第一弾は、部分型についてでした。
自分は静的型付け言語を触るのが初めてで、この部分型の概念を理解するのにかなり苦労しました。ただ、オブジェクト指向の継承の概念と似ているということに気づいてからは、割とすんなり理解することができました。
本当は、関数の部分型についても書きたかった(ここは理解するのにもっと苦労した)のですが、そこまで書くとあまりにも長くなりすぎるので、今回はこのあたりで。機会があれば、関数の部分型についても記事にしようと思います。