TypeScript型システム入門

JavaScriptに静的な型チェックと高度なエディタ支援を提供するTypeScriptの根本的な仕組みやジェネリクスなどを図解で学びます。

学習をスタートする

ロードマップ

1

なぜTypeScriptが必要なのか?型消去の仕組み

現在、多くのWeb開発の現場で JavaScript に代わって TypeScript(タイプスクリプト) が標準として採用されています。 第1章では、TypeScriptを導入する根本的な理由と、ブラウザで実行されるまでに型情報がどう扱われるのかという「型消去(Type Erasure)」の仕組みについて図解で解説します。 1. JavaScriptの課題とTypeScriptの解決策 JavaScriptは 「動的型付け言語」 です。コードが実行されるタイミングで初めてデータの型が決まるため、スペルミスや想定外のデータ型によるバグが、実際に動かすまで(最悪の場合は本番環境でユーザーが使うまで)見つかりにくいという課題があります。 TypeScriptは、JavaScriptの上に 「静的型付け(コンパイル時のチェック)」 のレイヤーを追加した言語です。 * エディタの強力な支援: コードを書いている途中で入力候補(インテリセンス)が表示され、メソッド名の間違いなどをその場でエラーとして指摘してくれます。 * ドキュメントとしての型: 引数や返り値の型が明確になるため、コード自体が信頼できる設計書として機能します。 2. コンパイルと「型消去」の仕組み(図解) TypeScriptはそのままではブラウザや Node.js で動かすことができません。実行するには、コンパイラ(tsc やビルドツール)を使って JavaScript に変換するプロセスが必要です。 この変換の際、「TypeScriptの型情報はすべて消去される」 という重要な性質を持っています。 型消去(Type Erasure)が意味すること 1. 実行時のパフォーマンスに影響しない: 型チェックはあくまで「コンパイル(変換)するとき」に行われます。生成された JavaScript には型チェックを行うコードは残らないため、型を付けたからといって実行速度が遅くなることはありません。 2. 実行時に型チェックは行われない: ブラウザ上で動いている時は単なる JavaScript です。そのため、外部APIからの不正なレスポンスや、ユーザー入力による「予期しない型」が実行時に紛れ込むことは防げません。これらはバリデーションライブラリ(Zodなど)を用いて実行時にもチェックする必要があります。 3. 具体的なコードの比較(Before / After) 型情報がどのように消去されるのか、コンパイル前後のコードを比較すると一目瞭然です。 コンパイル前(TypeScript) コンパイル後(JavaScript) このように、型定義(interface や type)や型アノテーション(: string など)は綺麗に消去され、標準的な JavaScript のコードに変換されていることがわかります。 まとめ * TypeScriptはJavaScriptに 静的型チェック を導入し、バグを未然に防ぎエディタ支援を向上させる。 * コンパイル時に型チェックが行われた後、型情報はすべて除去されて純粋なJavaScriptに変換される(型消去)。 * 実行時には型情報がないため、実行速度は変わらないが、APIレスポンスなどの不確定なデータは実行時の検証(バリデーション)が必要である。 次は、TypeScriptの真骨頂である、再利用性の高い型安全なコードを書くための「ジェネリクス」について学びましょう!

2

ジェネリクス(Generics)と型安全性

TypeScriptでコンポーネントや関数を設計するとき、特定の型に依存せず、かつ型安全性を保ったまま再利用したいケースが多々あります。 この「型をパラメータ(引数)として扱う仕組み」を ジェネリクス(Generics:総称型) と呼びます。 第2章では、ジェネリクスの基礎から、実用的な使い方までを図解で分かりやすく学びます。 1. ジェネリクスとは? ジェネリクスは、関数やクラス、インターフェースを定義する際に、具体的な型(string や number など)をあらかじめ決めず、使うときに外から型を引数として渡す 仕組みです。 2. なぜジェネリクスが必要なのか? 例えば、「引数をそのまま返す関数(アイデンティティ関数)」を作るとします。 ✕ 悪い例:any を使う any を使うと、どんな型でも受け取れますが、型チェックが機能しなくなり、戻り値の型も any になってしまいます。 ◯ 良い例:ジェネリクスを使う を定義することで、渡された引数の型を記憶し、戻り値にもその型を伝播させます(T は Type の頭文字で、任意のアルファベットが使えます)。 3. 実用的なユースケース:APIのレスポンス ジェネリクスは、APIからデータを取得する際の共通ハンドラーなどで非常によく使われます。レスポンスの共通構造(data, error など)を維持したまま、中身の型を動的に定義できます。 4. ジェネリクスに制約をつける(extends) 何でも受け入れられるジェネリクスですが、時には「特定のプロパティを持っている型だけに制限したい」という場合があります。その際は extends を使って制約を加えます。 まとめ * ジェネリクス は、コードを再利用しつつ型安全性を担保するための仕組み。 * などの型パラメータを定義し、呼び出し時に具体的な型をバインドする。 * any を排除しつつ、柔軟な共通化(APIレスポンスのラッパーなど)を安全に実装できる。 * extends キーワードを用いて、受け取れる型に 制約 を設定することが可能。

3

実用的な Utility Types と Mapped Types

TypeScriptでは、既存の型をベースに新しい型を動的につくり出すための「型ユーティリティ」が標準で多数用意されています。これらを Utility Types(ユーティリティ型) と呼びます。 第3章では、日常のWeb開発で多用する重要な Utility Types と、それらの仕組みを支える Mapped Types(マップ型) について図解で分かりやすく学びます。 1. よく使われる代表的な Utility Types すでに定義したオブジェクトの型を変更して再利用したい場合、一から定義し直すのではなく、以下のユーティリティ型を使用します。 オブジェクト型を変換する Utility Types 2. 具体的なコード例とユースケース ① Partial(すべてをオプショナルにする) API経由でデータを部分更新(PATCH)する場合などに便利です。 ② Pick と Omit(抽出と除外) 「ユーザー登録時のデータ(ID不要)」や「公開プロフィール(メールアドレス除外)」などのパターンで使用します。 ③ Record(マップの型定義) 特定のキーセットと特定の値の型をマッピングしたオブジェクト型を作成します。 3. Mapped Types(マップ型)の仕組み なぜ Partial や Readonly のような型変換ができるのでしょうか? その背景には、オブジェクトのプロパティをループ処理して新しい型を生成する Mapped Types(マップ型) という強力な仕組みがあります。 * keyof T: 型 T のすべてのプロパティ名(キー)をユニオン型(例: 'id' 'name' 'email')として取得します。 * P in keyof T: そのユニオン型を一つずつループ処理します(JavaScriptの for (const key in obj) に似ています)。 * T[P]: プロパティ P に対応する値の型を取得します(ルックアップ型)。 Partial の実装をのぞいてみよう TypeScriptの標準ライブラリ(lib.es5.d.ts)に定義されている Partial の実装は以下の通りです。 このように、TypeScriptは単に型に「名前を付ける」だけでなく、プログラムのように「型から新しい型を計算して合成する」ことができるため、非常に高い表現力と安全性を両立できます。

4

高度な型システム(Conditional, Mapped Types)

TypeScriptの型システムは「静的な型定義」に留まらず、プログラムの論理に基づいて柔軟に型を変化・生成させることができる「プログラミング言語」のような機能を持っています。本章では、型安全性を極限まで高めるための高度な機能である Conditional Types、Mapped Types、および Template Literal Types を解説します。 1. Conditional Types(条件付き型) Conditional Types は、三項演算子(T extends U ? X : Y)のような構文を用いて、型が別の型を「継承(extends)しているか」に基づいて動的に型を分岐させる仕組みです。 Conditional Types の型解決フロー(図解) 応用例:関数の引数による戻り値の動的切り替え API通信において、特定のステータスが成功であるか失敗であるかに応じて、レスポンスの型を切り替える例です。 2. Mapped Types(マッピングされた型) Mapped Types は、既存の型(オブジェクトやユニオン型)のすべてのキーをループ処理して、新しいオブジェクト型を生成する仕組みです。 修飾子(+, -, ?, readonly)の制御 Mapped Types では、プロパティを「読み取り専用 (readonly)」にしたり、「オプショナル (?)」にしたりする修飾子を、追加(+)または削除(-)することができます。 3. Template Literal Types(テンプレートリテラル型) ES6 のテンプレートリテラルに似た構文で、文字列リテラル型を組み合わせて新しい文字列型を動的に生成する機能です。 これを利用すると、CSSクラス名やアクションタイプ名などのパターンを厳密に型安全に縛り上げることができます。 まとめ * Conditional Types (T extends U ? X : Y) は、条件分岐によって型を動的に切り替える。 * Mapped Types ([K in keyof T]) は、既存のオブジェクト型のキーを再利用して新しい型を作る。 * Template Literal Types は、文字列型同士を合成してパターンに基づいた厳格な型を作成する。

5

tsconfigの設定とモジュール解釈の極意

TypeScriptプロジェクトの頭脳であり、コンパイラ(tsc)の挙動を決定づけるのが tsconfig.json です。この設定ファイルのオプション、特にモジュールをどのように解決するかという 「モジュール解釈(Module Resolution)」 の理解は、環境構築時のコンパイルエラーを回避するために極めて重要です。 第5章では、tsconfig.json の主要オプションとモジュールシステムへの適合について学びます。 1. tsconfig.json の主要オプション TypeScriptを導入する際、最も重要とされる主要な設定項目です。 * target: * TypeScriptコードをどのバージョンのJavaScriptにコンパイル(変換)するかを指定します(例: ES2022, ESNext)。 * lib: * コンパイル時に利用可能な組み込みAPIの型定義をコンパイラに伝えます(例: ブラウザ環境であれば ["DOM", "ES2022"])。 * strict: * true にすることで、noImplicitAny(暗黙的なanyの禁止)や strictNullChecks(厳密なnullチェック)など、すべての厳格な型チェック機能が一括で有効化されます。製品の品質を保つために 必須 の設定です。 2. ESModules vs CommonJS とモジュール解釈 JavaScriptには歴史的に2つのモジュールシステムが存在します。 * CommonJS (CJS): require() / module.exports (旧来のNode.js標準) * ESModules (ESM): import / export (ブラウザ標準およびモダンNode.js標準) これらが混在したプロジェクトや、外部ライブラリをロードする際、コンパイラがファイルを正しく探索・解釈できるようにするのが moduleResolution オプションです。 * node (または node10): 古いNode.jsの解決方法です。フォルダ内の index.ts や拡張子の省略を許容します。 * nodenext / node16: モダンなNode.jsの動作を再現します。package.json の "type": "module" や "exports" フィールドによる正確なサブパス解決に対応し、相対パスのインポート時に拡張子(.js)の明示が必要になります。 * bundler: WebpackやVite、Next.jsなどの外部バンドラーの利用を前提とした設定です。拡張子の省略や、Node.js仕様のモジュール探索を適切に両立します。 3. パスエイリアスと型定義ファイル パスエイリアス (paths) 相対パスの階層が深くなったときの ../../../../components/Button のようなネスト(相対パス地獄)を解消し、プロジェクトのルートからの絶対パス風に記述できるようにする機能です。 これにより、どこからでも import { Button } from '@/components/Button' のように美しくインポートできます。 型定義ファイル (.d.ts) JavaScriptで書かれたライブラリに対して、TypeScriptから型情報を提供するためのファイルです。 * アンビエント宣言 (declare): declare module 'some-js-library' と記述することで、型定義が存在しない古いJavaScriptライブラリに対しても、一時的にエラーを回避させて型を当てることができます。