TypeScriptの型ガード・ユニオン型・リテラル型まとめ!型安全な実装の基本を徹底解説
生徒
「TypeScriptでデータの種類ごとに処理を変えることってできますか?」
先生
「もちろんできます。TypeScriptではユニオン型やリテラル型、そして型ガードを使って、安全に判定できます。」
生徒
「型ガードって何ですか?想像がつきません。」
先生
「型ガードとは、データがどの型なのかを見分けるための仕組みです。実際の例を見ながら理解してみましょう。」
1. ユニオン型とは?
TypeScriptのユニオン型は、ひとつの変数に複数の型を持たせられる機能です。ユニオンは英語で「まとめる」「一つにする」という意味で、例えば、numberとstringの両方を扱いたいときに役立ちます。
プログラミング未経験の方は、ユニオン型を「入れ物に入る荷物の種類が複数ある状態」と考えると理解しやすいと思います。箱に本か洋服のどちらかを入れるように、変数に数字か文字のどちらかを入れることができます。
let value: number | string;
value = 100;
value = "こんにちは";
2. リテラル型とは?
リテラル型とは、値を特定の決められたものだけに限定する型です。例えば、「赤」「青」「黄色」の三つの言葉だけを許可したい場合に使います。ゲームで「初級」「中級」「上級」の三つの難易度しか選べないようにするイメージです。
let level: "初級" | "中級" | "上級";
level = "中級"; // OK
// level = "神級"; // エラー
3. 型ガードとは?
型ガードとは、実行中に「今の値はどの型か?」を判定して、適切な処理へ導く仕組みです。ユニオン型だけでは、変数の中身が数字か文字か判断できず、そのまま計算や文字操作ができません。
そこで、型ガードを使って型を判定すれば、エラーを避けて安全に処理できます。
function print(value: number | string) {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
console.log(value * 2);
}
}
print("apple"); // APPLE
print(10); // 20
4. 独自の型ガード(自作型ガード関数)
TypeScriptでは、isキーワードを使って独自の型ガード関数を作れます。複雑なオブジェクトやAPIレスポンスの判定に便利です。
type Dog = { type: "dog", bark: () => void };
type Cat = { type: "cat", meow: () => void };
function isDog(animal: Dog | Cat): animal is Dog {
return animal.type === "dog";
}
function call(animal: Dog | Cat) {
if (isDog(animal)) {
animal.bark();
} else {
animal.meow();
}
}
5. 型ガード・ユニオン型・リテラル型の使いどころ
TypeScriptで型の安全性を高めると、予期せぬエラーを防ぎ、安心して開発できます。特に以下の場面で役に立ちます。
- 入力フォームなどでユーザーの入力がどの型か分からないとき
- APIレスポンスが複数パターンあるとき
- ゲームや画面状態の表示切替を管理するとき
type Screen = "loading" | "success" | "error";
function show(screen: Screen) {
if (screen === "loading") console.log("読み込み中です…");
if (screen === "success") console.log("成功しました!");
if (screen === "error") console.log("エラーが発生しました");
}
このように、ユニオン型とリテラル型で表示できる状態を制限し、型ガードで正しい処理を実行すれば、安全でバグの少ないコードを書けるようになります。
まとめ
TypeScriptにおける型安全な設計の重要性
ここまで、TypeScriptのユニオン型、リテラル型、そして型ガードについて順番に学んできました。これらはすべて、TypeScriptが持つ「型安全」という大きな特徴を支える重要な仕組みです。JavaScriptでは実行してみないと分からなかったエラーも、TypeScriptを使えば事前に気づくことができます。その中でも、複数の型を扱う場面で特に力を発揮するのがユニオン型と型ガードです。
ユニオン型を使うことで、「この変数には数値か文字列のどちらかが入る」といった柔軟な設計が可能になります。一方で、柔軟さだけでは安全とは言えません。そこでリテラル型を組み合わせることで、許可された値だけに制限し、意図しない値が入るのを防ぎます。さらに型ガードを使えば、実行時に正しい型を判定し、その型に応じた安全な処理を書くことができます。
ユニオン型とリテラル型を組み合わせた設計
実際の開発現場では、ユニオン型とリテラル型は単独で使われるよりも、組み合わせて使われることがほとんどです。例えば、画面の状態管理や処理の分岐、APIレスポンスの判定など、状態が限定されているケースではリテラル型が非常に有効です。これにより、「想定外の状態」がコードに入り込む余地を減らすことができます。
type Status = "idle" | "loading" | "success" | "error";
function changeStatus(status: Status) {
if (status === "idle") {
console.log("待機中です");
} else if (status === "loading") {
console.log("処理を実行しています");
} else if (status === "success") {
console.log("処理が完了しました");
} else {
console.log("問題が発生しました");
}
}
このような書き方をすることで、状態管理が明確になり、後からコードを読む人にも意図が伝わりやすくなります。TypeScriptの型定義は、ドキュメントの役割も果たすため、チーム開発でも大きなメリットがあります。
型ガードで実現する安全な分岐処理
型ガードは、ユニオン型とセットで考えることが大切です。ユニオン型だけでは「どの型か分からない状態」が残りますが、型ガードを使えばその不安を解消できます。typeof演算子やin演算子、独自に作成した型ガード関数など、用途に応じて使い分けることが重要です。
type Result =
| { kind: "success"; message: string }
| { kind: "error"; errorCode: number };
function handleResult(result: Result) {
if (result.kind === "success") {
console.log(result.message);
} else {
console.log("エラーコード:", result.errorCode);
}
}
この例では、リテラル型であるkindの値を判定することで、TypeScriptが自動的に型を絞り込み、安全にプロパティへアクセスできるようになります。この仕組みを理解すると、複雑な条件分岐も安心して書けるようになります。
学習のポイントと実務への活かし方
初心者の方は、まず「ユニオン型で複数の型を扱える」という点を理解し、その次に「リテラル型で値を限定できる」という考え方を身につけるとよいでしょう。そして最後に、型ガードによって安全に分岐処理を書く流れを意識すると、TypeScriptらしいコードが自然に書けるようになります。
小さなサンプルでも構わないので、実際に手を動かしながら試すことが大切です。型エラーが出たときに「なぜこのエラーが出ているのか」を考えることが、理解を深める近道になります。
生徒
「ユニオン型って、最初は難しそうに感じましたけど、複数の型をまとめて扱える便利な仕組みなんですね。」
先生
「そうですね。ただし、まとめただけでは安全とは言えないので、型ガードでしっかり判定することが大切です。」
生徒
「リテラル型を使うと、値の候補を限定できるのも分かりました。状態管理に向いていそうですね。」
先生
「その通りです。リテラル型と型ガードを組み合わせると、読みやすくてバグの少ないコードになります。」
生徒
「TypeScriptはエラーが多くて大変だと思っていましたけど、実は守ってくれる存在なんですね。」
先生
「まさにその理解が大切です。型を味方につけることで、安心して開発できるようになりますよ。」