はじめに
今回の記事では、TypeScript の型システムについて、TypeScript を初めて触る人が押さえておくと良い、基礎的な内容を説明します。
型アノテーション
はじめに、TypeScript で扱える型について説明します。
プリミティブ型
JavaScript のプリミティブ型は、TypeScript では string 、number 、boolean として用意されています。
型アノテーションは、以下のように :TypeAnnotation 構文で書きます。
1 | var val: number = 1; |
リテラル型
ある変数を、特定の文字列のみを受け付けるようにしたい場合、文字列リテラルを型として使うことができます。
1 2 | gender: 'male' | 'female'; var gender = 'male'; |
また、文字列の他に、number 型と boolean 型でも同様に型として使うことができます。
any
any 型はすべての型と互換性があります。そのため、 any 型の変数にはすべての型を代入することができ、他のどの型の変数にも any 型の変数を代入することができます。
1 2 3 | var hoge: any; hoge = 1; hoge = '1'; |
nullとundefined
null と undefined は、any 型と同じように扱われます。この二つは他のどの型の変数にも代入することができます。( tsconfig.json の strictNullChecks が true の場合はエラーになります)
1 2 3 4 | var num: number; var str: string: num = null; str = undefined; |
ユニオン型
ある変数に複数の型を代入できるようにしたい場合、 | を使って書くことができます。
1 | var hoge: string | number; |
上の例では、 hoge には string 型、または number 型の値を代入することができます。また、オブジェクト型も同様に、union 型として定義することができます。
1 2 3 4 5 6 7 | interface Hoge { a: number; } interface Fuga { b: number; } var hogeFuga: Hoge | Fuga; |
上の例では、 hogeFuga は、 Hoge 、または Fuga を代入することができますが、それぞれが持っているパラメータが異なるため、後述する型ガードと合わせて使われることが多いでしょう。
交差型
交差型では、異なる複数のオブジェクトの機能を併せ持つ新しいオブジェクトを作成することができます。
1 2 3 4 5 6 7 8 9 10 11 | interface Hoge { a: number; } interface Fuga { b: number; } var hogeFuga: Hoge & Fuga; hogeFuga = { a: 1, b: 2 } |
上の例では、 hogeFuga は Hoge が持っている a というプロパティと、 Fuga が持っている b というプロパティを持っています。
タプル型
タプルの実態は配列ですが、以下のようにアノテーションをつけることができます。
1 2 | var hoge: [number, string]; hoge = [1, '1']; |
列挙型
JavaScript には列挙型(enum)はありませんが、TypeScript では以下のように書くことができます。
1 2 3 4 5 | enum Colors { Red, Green, Blue } |
これらの値は0からインクリメントされる数値です。以下のように、始めの値を指定することができます。
1 2 3 4 5 | enum Colors { Red = 5, Green, Blue } |
また、値を文字列にすることもできます。
1 2 3 4 5 | enum Colors { Red = 'red', Green = 'green', Blue = 'blue' } |
ほかにも、静的関数をもたせることなどもできます。より詳しく知りたい方はこちらをご覧ください。
配列
配列の宣言は、有効な型アノテーションの後ろに [] をつけます。
1 | var numbers: number[]; |
interface
interface は、複数の型アノテーションを一つのアノテーションに合成するために使われます。
1 2 3 4 5 | interface Profile { name: string; age: number; gender: 'male' | 'female'; } |
なお、あるクラスに interface を準拠させたい場合、 implementes キーワードを使うことができます。
1 2 3 4 5 6 7 8 9 10 11 | interface Profile { name: string; age: number; gender: 'male' | 'female'; } class CProfile implements Profile { name: string; age: number; gender: 'male' | 'female'; } |
インライン型アノテーション
1度だけ使われるアノテーションの場合、インライン型で定義することで型名を考える手間を省くことができます。
1 2 3 4 5 | var profile: { name: string; age: number; gender: 'male' | 'female'; } |
型エイリアス
以下のように、型アノテーションに名前をつけることができます。
1 2 | type Gender = 'male' | 'female'; var gender: Gender; |
generics
generics な関数は以下のように定義します。
1 2 3 4 5 6 7 8 9 10 | function reverse<T> (items: T[]): T[] { var tmp = []; for (let i = items.length - 1; i >= 0; i++) { tmp.push(items[i]); } return tmp; } var numbers = [1, 2, 3]; var rNumbers = reverse(numbers); |
generics についてもっと詳しく知りたい方は、こちらをご覧ください。
関数の型
関数のパラメータにつけることができる型アノテーションについて説明します。
引数と戻り値の型アノテーション
関数には、引数と戻り値に型アノテーションをつけることができます。
1 2 3 4 | // 関数の型アノテーション(戻り値は number型) function square(val: number): number { return val * val; } |
また、関数が値を返す必要がない場合、:void 型アノテーションをつけることができます。
1 2 3 | function alert(val: string): void { alert(val); } |
なお、コンパイラが型推論できる場合、戻り値のアノテーションを省略することができます。
オプションパラメータ
以下のように省略可能なパラメータを定義することができます。
1 2 3 4 5 6 7 8 | function hoge(a: number, b?: number) {} hoge(1); hoge(1, 2); // デフォルト値を設定することもできる function huga(a: number, b: number = 2) {} huga(1); huga(1, 2); |
overload
以下のように関数の overload を宣言することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function hoge(abcd: number); function hoge(ac: number, bd: number); function hoge(a: number, b: number, c: number, d: number); function hoge(a: number, b?: number, c?: number, d?: number) { if (!b && !c && !d) { b = c = d = a; } else if (!c && !d) { c = a; d = b; } return { a: a, b: b, c: c, d: d }; } |
上の例では、関数 hoge は引数を1つ、または2つ、または4つ受け付けます。引数を3つ渡した場合はエラーになります。
型推論
変数に型アノテーションを書かなくても、いくつかのルールに基づいて自動的に型を推論します。
変数の定義
変数宣言時の初期値によって型を推論します。
1 2 3 | let hoge = 1; // number型 let fuga = '1'; // string型 hoge = fuga; // error |
戻り値
return で返却する値によって型を推論します。
1 2 3 4 5 6 | function hogeFunction() { return 'hoge'; } var hoge = hogeFunction(); // number型 hoge = '1'; hoge = 1; // error |
代入
代入される値によって型を推論します。
1 2 3 4 | let hoge = 1; let fuga = hoge; // number型 fuga = 1; fuga = '1' // error |
構造化
構造化も同様に値によって型を推論します。
1 2 3 | let hoge = { a: 1, b: '1' } // let hoge: { a: number, b: string } = { a: 1, b: '1' } と同じ hoge.a = 2; hoge.a = '2'; // error |
分解
分解も同様に値によって型を推論します。
1 2 3 4 | let hoge = { a: 1, b: '1' } let { a } = hoge; // number型 a = 2; a = '2'; // error |
型アサーション
型アサーションは、TypeScript が推論した型を任意の型で上書きすることです。一般的には、JavaScript で書かれたコードを TypeScript へ移植する場合に用いられることが多いです。
1 2 3 4 5 6 7 8 | interface Hoge { a: number; b: string; } var hoge = {} as Hoge; hoge.a = 1; hoge.b = '1'; |
注意点としては、必要なパラメータを設定し忘れても、コンパイラはエラーを表示しないため、予期せぬエラーを引き起こすことがあることです。可能であれば以下のようにするのが良いでしょう。
1 | var hoge: Hoge = { a: 1, b: '1' }; |
型アサーションの成否
型アサーションは、型T が 型U のサブタイプであるか、その逆である場合に成功します。
1 2 3 4 5 6 7 8 9 10 11 12 | interface Hoge { a: number; b: string; } var hoge1: Hoge = { a: 1 } as Hoge; hoge1.a = 2; hoge1.b = '2'; var hoge2: Hoge = { a: 1, b: '1', c: true } as Hoge; hoge2.a = 2; hoge2.b = '2'; hoge2.c = false; // error |
型ガード
Type Guard を使用すると、ブロック内のオブジェクトの型を制限することができます。
typeof
1 2 3 4 5 6 7 | var hoge: number | string; if (typeof hoge === 'number') { hoge += 1; hoge.substr(1); // error } else if (typeof hoge === 'string') { hoge.substr(1); } |
instanceof
クラスの場合は instanceof を使います。
1 2 3 4 5 6 7 | class Hoge { a: number; } class Fuga { b: number; } var hoge: Hoge; if (hoge instanceof Hoge) { hoge.a += 1; hoge.b += 1; // error } |
in
in 演算子はオブジェクトのプロパティをチェックします。これも TypeGuard として使用することができます。
1 2 3 4 5 6 | interface Hoge { a: number; } interface Fuga { b: number; } var hoge: Hoge; if ('a' in hoge) { hoge.a += 1; } |
さいごに
TypeScript 初学者向けに TypeScript の型システムについて紹介しました。まだ、 TypeScript を使っていない方は、ぜひ、 TypeScript を初めてみてください。