この記事は何?
本記事は、動的型付け言語メインで開発してきた筆者が、長年「何だこれ」と思いつつ放置してきたTypeScriptの呪文のような記述について理解したので、その気づきを初心者向けに共有するものです。
const firstNumber = getFirstElement<number>([1, 2, 3]);
※このコードを初めて見た時の筆者の感想
「この<number>
がホントになぞ。
引数でもないし変数でもない。関数の処理にも使われてない。
でもあちこちに出てくるから何かを意味してはいるみたい。意味わからん。」
結論
<number>
はジェネリクス型と呼ばれるものです。
(厳密にいうとジェネリクス型を継承した型となります。)
ジェネリクス型とは 使うタイミングで自由に型を決められる、仮置きの型 のことです。
専門用語だと抽象化のために使う型になります。
解説
初心者だと何を言ってるかわからないと思いますが、<number>
のような書き方は、
定義部分ではJavaScript(動的型付言語)っぽく型なしで書いておいて、使う時だけTypeScript(静的型付言語)の恩恵を受ける、良いとこ取りの書き方
となります。
以下で使用例を使って説明します。
使用例を見ながらの説明
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
const firstNumber = getFirstElement<number>([1, 2, 3]);
console.log(firstNumber); // 1
const firstString = getFirstElement<string>(["a", "b", "c"]);
console.log(firstString); // "a"
まず最初に「いきなり出てくるTってなんだよ」と思うかと思います。
このTは本記事の主役のジェネリクス型と呼ばれる型となります。
TとはTypeの頭文字で、「なんでもいいんだけど皆そうしてるから」という理由でTという文字を使うそうです。(極論AAA
とかでもいいものです)
そしてTとは、使われる時にどんな型でも入れていいよという書き方です。
「なんで意味をなさない型を定義した?意味ないじゃん、引数の型違うんだからanyでいいじゃん!」と思うかもしれません。
その通りです。JavaScript的な使い方になりますがそれでちゃんと動きます。
でも落ち着いてください。
anyの場合関数を使う時に引数の型を固定しなくてよくなるため、1(数字)と"1"(文字)を入れ間違っても気づくことができず、静的型付け言語の良さを使うことができなくなります。
そこで、何かしらの型は絶対いるけど後から好きな型を使えるようにした便利機能がこのT(ジェネリクス型)となっています。
そしてこの機能を使うと柔軟に静的型付言語を操ることができるようになるため、ソースコードの可読性を犠牲にしてまで利用されているような代物となっています。
// === ジェネリクスの書き方 ===
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
const resultGeneric = getFirstElement<string>(["1", "2", "3"]);
console.log(resultGeneric); // "1"
// === anyの書き方 ===
function getFirstElementAny(arr: any[]): any {
return arr[0];
}
const resultAny = getFirstElementAny([1, "2", true]);
console.log(resultAny); // 1
// ↑上記のようにanyだと配列の中がガチャガチャでもエラーが出ずに使えるため、
// 本当は文字列の"1"が欲しい時に数字の1が意図せず渡されてしまう恐れがあります。
// 動的型付け言語だと特に複雑なロジックではこのような部分を見落として本番でのエラーが起きやすいのですが、それを防ぎやすくなっています。
※寄り道の補足(本筋から外れるので興味がある方だけご覧ください)
getFirstElement<number>([1, 2, 3]);
のnumber
はどこから出てきた?と初心者の時の筆者なら思ってるかもしれないので書いておきます。
numberやstringは、言語に最初から組み込まれている基本的な型です。TypeScriptを導入したときにダウンロードされる型定義ファイル(通常は目に見えない場所に保存されているファイル)により、これらが利用できます。
もし手作りの型を指定したければ以下のようにして使うことができます
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
type testType = string|number; // 手作りの型を定義
const firstNumber = getFirstElement<testType>([1, "2", 3]); // 手作りの型を使用
console.log(firstNumber); // 1 (エラーが出ずに出力される)
まとめ
-
<number>
はジェネリクス型(を継承した型) - ジェネリクス型は何かしら型を使うことは強制するけど、使う型は呼び出しの場面で決められる仕組み
- anyに似てるけど、コンパイルエラーをしっかり活用できるのがジェネリクス型
- ジェネリクス型は処理を共通化した時に多様化する引数の型定義の悩みを解消する仕組み
以上が<number>
およびジェネリクス型の正体です。
これを理解できればソースコードが読みやすくなると思います。
さらに使いこなすことができれば、より保守性の高いコードを実装できるようになると思います。