TypeScriptの型応用入門 — ユニオン型・ジェネリクス・ユーティリティ型の全体像
TypeScriptの基本的な型は覚えたけど、実際のコードに出てくる <T> や | を見ると「これ何…?」となること、ありますよね。
string | number って書いてあるけど、これどういう意味?
<T> が付いてるの、読もうとしても全然わからない…
この記事では、TypeScriptの「型の応用」に入ったときに最初につまずきやすいテーマをまとめて整理します。ユニオン型と型ガード、ジェネリクスの考え方、enumとas constの使い分けの概要、ユーティリティ型の全体像といった内容を、順番に解説していきます。
この記事は次のような方におすすめです。
- string / number / boolean などの基本の型は理解している
- interfaceやtypeエイリアスは一通り触れたことがある
- 実際のコードで
<T>や|を見て意味が追えなかった経験がある - 「型の基礎の次に何を学べばいいか」を整理したい
この記事を読むと、TypeScriptの型応用機能がどんな構造になっているかの全体像がつかめます。それぞれのテーマで「何のためにあるか」がわかれば、各機能を詳しく学ぶときにぐっとスムーズになります。
それでは、順を追って詳しく見ていきましょう!
ユニオン型と型ガード — 複数の型を受け入れて安全に絞り込む
ユニオン型は「この変数にはstringかnumberのどちらかが入る」という書き方をするときに使います。柔軟に見えますが、実は「そのまま使うと型エラーが出る」という問題が生まれます。その解決策が型ガードです。この2つはセットで理解するのがポイントです。
ユニオン型の基本的な書き方
ユニオン型は |(パイプ)を使って複数の型を並べる構文です。
// 引数が string か number のどちらかを受け取る関数
function printValue(value: string | number): void {
console.log(value);
}
printValue("hello"); // OK
printValue(42); // OK
string | number のように書くと「どちらの型も受け入れる」という意味になります。 関数の引数はもちろん、変数の型定義にも使えます。
let id: string | number;
id = "user-001"; // OK
id = 123; // OK
ユニオン型を使うとエラーが出る場面と、型ガードの必要性
ここが最初のつまずきポイントです。ユニオン型で受け取った値に対して、型固有のメソッドを呼ぼうとするとエラーになります。
function printValue(value: string | number): void {
console.log(value.toUpperCase()); // エラー!
// Property 'toUpperCase' does not exist on type 'string | number'
}
toUpperCase() はstringにしかないメソッドです。TypeScriptは「valueがnumberかもしれない」と知っているので、エラーを出します。
TypeScriptに「今この時点ではstringだよ」と伝える必要があります。 それが型ガードの役割です。
typeofを使った型ガードの基本
型ガードとは、「ある条件の中ではこの型だ」とTypeScriptに認識させる書き方です。typeof を使った条件分岐が最もシンプルなパターンです。
function printValue(value: string | number): void {
if (typeof value === "string") {
// このブロックの中では、value は string 型として扱われる
console.log(value.toUpperCase()); // OK
} else {
// このブロックの中では、value は number 型として扱われる
console.log(value.toFixed(2)); // OK
}
}
if (typeof value === "string") の条件分岐の中では、TypeScriptがvalueをstring型として認識してくれます。これを「型の絞り込み(narrowing)」と言います。
整理すると、ユニオン型で「どちらの型も受け入れる」を実現して、型ガードで「今どの型かを確定させる」という2段階で使います。
ジェネリクスとは — 型を「後から決める」仕組み
ジェネリクスは「型の引数」を持つ仕組みです。「型を引数として受け取る」とはどういうことか、問題から考えると理解しやすくなります。
ジェネリクスを使わないとどうなるか
たとえば、配列から先頭の要素を返すだけのシンプルな関数を書くとします。
// string の配列用
function firstString(arr: string[]): string {
return arr[0];
}
// number の配列用
function firstNumber(arr: number[]): number {
return arr[0];
}
やっていることは同じなのに、型ごとに関数を作らないといけません。any を使えば1つにまとめられますが、今度は型の情報が失われます。
function first(arr: any[]): any {
return arr[0];
}
const result = first(["a", "b", "c"]);
// result の型が any になってしまう
// 戻り値が string だとわかっているのに、TypeScript が追跡してくれない
any を使うと「TypeScriptが型を追跡できなくなる」という問題が起きます。 これがジェネリクスの必要性が生まれる理由です。
<T> の読み方と基本構文
ジェネリクスは <T> という記法で型を「後から受け取る」ことができます。
function first<T>(arr: T[]): T {
return arr[0];
}
<T> は「型の仮引数」です。関数を呼び出すときに型が決まります。T は慣例的な名前で、実際には任意の名前が使えます(Item や Value でも構いません)。
ジェネリクスを使うとどう変わるか
先ほどの first 関数を使ってみます。
const result1 = first(["a", "b", "c"]); // result1 の型は string
const result2 = first([1, 2, 3]); // result2 の型は number
TypeScriptが引数の型から T を自動的に推論してくれるので、first<string>(...) のように明示しなくても動きます。戻り値の型も string や number として正しく認識されます。
「同じ処理を型ごとに書き分けなくていい、かつ型の情報も失われない」というのがジェネリクスのメリットです。
T extends ... を使う書き方)や複数の型引数を使う応用は、別の記事で詳しく解説しています。
enumとas const — 列挙型の使い方と使い分けの概要
「決まった値のセット」を定義したいときに使うのがenumです。JavaScriptにはない機能ですが、「非推奨では?」という話を見かけることもあります。ここではその背景をざっくり整理します。
enumの基本と使いどころ
enumは「いくつかの決まった値の中から1つを選ぶ」型を作るときに使います。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
function move(dir: Direction): void {
console.log(dir);
}
move(Direction.Up); // "UP"
方角・ステータス・処理結果など、「取りうる値が固定されている」ケースで使われます。定義した値以外は渡せないので、うっかりミスを防げます。
as constとの使い分けを一言で整理する
enumの問題点の1つは、「JavaScriptにコンパイルすると余分なコードが生成される」点です。その代替としてよく使われるのが as const を使ったオブジェクトです。
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT",
} as const;
type Direction = typeof Direction[keyof typeof Direction];
// "UP" | "DOWN" | "LEFT" | "RIGHT" というユニオン型になる
「enumは手軽だが生成コードが増える。as constはオブジェクトベースで軽量」という感覚的な差があります。
ユーティリティ型とは — TypeScript組み込みの型変換ツール
ユーティリティ型は、TypeScriptに最初から組み込まれている「型を変換するツール群」です。既存の型を元に「一部をオプショナルにした型」「一部のプロパティだけ取り出した型」などを作ることができます。
よく使うユーティリティ型の一覧
| 型名 | 概要 | よく使う場面のイメージ |
|---|---|---|
Partial<T> |
全プロパティをオプショナルにする | 更新APIの引数など「一部だけ渡す」ケース |
Required<T> |
全プロパティを必須にする | オプショナルを消したい場面 |
Readonly<T> |
全プロパティを読み取り専用にする | 変更不可のデータを扱う場面 |
Pick<T, K> |
指定したプロパティだけを取り出す | 必要な項目だけの型を作りたい場面 |
Omit<T, K> |
指定したプロパティを除外する | 特定のキーだけ省いた型を作りたい場面 |
Record<K, V> |
キーと値の型を指定したオブジェクト型を作る | マップ・辞書型の型定義 |
簡単なコード例を見てみましょう。
interface User {
id: number;
name: string;
email: string;
}
// id と name だけを持つ型を作る
type UserPreview = Pick<User, "id" | "name">;
// → { id: number; name: string; }
// 全プロパティをオプショナルにした型を作る
type PartialUser = Partial<User>;
// → { id?: number; name?: string; email?: string; }
既存の型を元に、派生した型を手軽に作れるのが特徴です。
ユーティリティ型はなぜ便利か
たとえばAPIのレスポンス型と更新リクエストの型が「ほぼ同じだけど一部違う」という状況はよくあります。
ユーティリティ型を使うと、似た型を何度も手書きせずに既存の型から派生させられます。 型の定義が1か所にまとまるので、型が変わったときの修正コストも下がります。
ユーティリティ型は「暗記するもの」というより「一覧を見て必要なときに使うもの」だと思っておくと楽です。
タグ付きユニオン(Discriminated Union)とは何か
ユニオン型をより安全に使いこなすパターンとして、「タグ付きユニオン」があります。
ユニオン型で複数の型を受け入れるとき、「今どの型なのか」を識別するための共通プロパティ(タグ)を持たせる、という設計です。
type SuccessResult = {
status: "success"; // タグ(識別用のリテラル型)
data: string;
};
type ErrorResult = {
status: "error"; // タグ
message: string;
};
type Result = SuccessResult | ErrorResult;
status というリテラル型のプロパティを共通で持つことで、status === "success" の条件分岐の中でTypeScriptが型を自動的に絞り込んでくれます。
never型とは — 「ありえない型」が役立つ場面
never型は「値が存在しない」「絶対に到達しない」状態を表す型です。void(何も返さない)とは意味が違います。
void:関数が「値を返さない」(処理は終わる)never:関数が「絶対に正常終了しない」(例外を投げるなど)
// void の例:戻り値がない関数
function log(message: string): void {
console.log(message);
}
// never の例:必ず例外を投げる関数
function throwError(message: string): never {
throw new Error(message);
// この行には絶対に到達しない
}
never型が実用的に使われる場面の1つが、ユニオン型の「全パターンを網羅しているか」のチェックです。
type Color = "red" | "blue" | "green";
function handleColor(color: Color): string {
if (color === "red") return "赤";
if (color === "blue") return "青";
if (color === "green") return "緑";
// ここに到達することはないはず → never で網羅チェック
const _exhaustive: never = color;
return _exhaustive;
}
後から Color に新しい値を追加したときに、ここでエラーが出るようになります。処理漏れを型レベルで防げるのが便利なところです。
まとめ — ピラー6で扱った型応用の全体像
この記事では、TypeScriptの「型の応用」に入ったときに出会う主な機能を一通り見てきました。
- ユニオン型は「複数の型のどちらかを受け入れる」書き方。
|でつなぐ - 型ガードは「今どの型かをTypeScriptに教える」仕組み。
typeofを使った条件分岐が基本 - ジェネリクスは「型を後から受け取る」仕組み。
<T>を使って汎用的な関数・型を作れる - enumは「決まった値のセット」を定義する型。as constによる代替パターンもある
- ユーティリティ型は「既存の型を変換するツール群」。Partial・Pick・Omit・Recordなどがある
- タグ付きユニオンは、共通のリテラル型プロパティを使ってユニオン型の絞り込みを安全にするパターン
- never型は「到達しない」を表す型で、ユニオン型の網羅チェックに使える
「型の基礎(string / number / booleanやinterfaceなど)」を押さえた次のステップとして、この記事で挙げた機能がどんな場面で役立つかのイメージが持てれば十分です。





