【TypeScript】型ガードの使い方と型の絞り込み完全ガイド

【TypeScript】型ガードの使い方と型の絞り込み完全ガイド

PR Amazonのアソシエイトとして、ITナレッジライフは適格販売により収入を得ています。

記事の文字数:3,863

TypeScriptの型ガード(Type Guard)の使い方を初心者向けに解説。typeof・instanceof・in演算子による基本的な型の絞り込みから、型述語(is)を使ったユーザー定義型ガードの実装、assertsによるアサーション関数まで実例コードを交えて徹底解説します。

ITエンジニアにお勧めの本 ↗

おすすめ

エンジニアが知っておきたい思考の整理術 複雑な情報を【理解する】【伝える】テクニック

難易度
実用性
図解

思考の整理というテーマを、実務ですぐに使える具体的な「技術」として落とし込んだ一冊です。

ベストセラー

世界一流エンジニアの思考法

難易度
実用性
思考法

技術スキル以上に大切な「考え方」をアップデートできる。世界基準の効率的な仕事術が学べます。

文系社会人のためのIT資格の歩き方: 文系のあなたに最適な道をご提案します (YKcreate kiki)

難易度
実用性
キャリア度

ロードマップが非常に明確。キャリアプランを見据えた、無駄のない学習順序を提案してくれます。

エンジニアを説明上手にする本 相手に応じた技術情報や知識の伝え方

難易度
実用性
コミュ力

「伝える技術」もエンジニアの重要なスキル。相手の視点に立った、具体的な説明のコツが満載です。

技術広報入門 ー テックブログから始めるエンジニアカルチャーのつくり方

難易度
実用性
独自性

テックブログを入口にエンジニア組織の文化をどう設計し、育てていくかという実践論が学べます。

TypeScriptTypeScript [タイプスクリプト]JavaScriptに型システムを追加した言語で開発していると、ユニオン型(string | number や User | null など)を扱う場面が日常的に登場します。しかし、そのままプロパティやメソッドにアクセスしようとすると、コンパイルエラーが発生して困った経験はないでしょうか。

そこで活躍するのが 型ガード(Type Guard) です。本記事では、TypeScriptにおける型ガードの基本的な使い方から、実務で頻出するユーザー定義型ガード(型述語 is)、アサーション関数(アサーションシグネチャ asserts)の実装パターンまで、コード例とともに詳しく解説します。

TypeScriptの型ガード(Type Guard)とは?

型ガードとは、条件分岐などのロジックを通じて特定のスコープ内における変数の型を絞り込む(Narrowing)仕組みのことです。

なぜ型ガードが必要なのか?

TypeScriptの型チェッカーは静的解析によって動作します。実行時に入る値の型が複数考えられる場合(ユニオン型など)、そのままでは共通のプロパティ以外に安全にアクセスできません。

function printLength(value: string | number) {
// コンパイルエラー: Property 'length' does not exist on type 'number'.
console.log(value.length);
}

上記コードでは、value が number 型の可能性もあるため、コンパイラが警告を出します。型ガードを用いることで、特定の分岐内では value が確実に string であるとコンパイラに認識させることができます。

function printLength(value: string | number) {
if (typeof value === "string") {
// このブロック内では value は確実に string 型として扱われる
console.log(value.length);
} else {
// このブロック内では value は number 型に絞り込まれる
console.log(value.toFixed(2));
}
}

型アサーション(as)との違い

TypeScriptには 型アサーション(as) もあります。型アサーションは、開発者がコンパイラに対して値の型を明示的に指定する仕組みです。ただし、実行時に値の検証を行うわけではありません。そのため、指定した型と実際の値が異なっていても、実行時にエラーが発生する可能性があります。

一方、型ガードは、実行時の値チェックに基づいてコンパイル時の型を絞り込む仕組みです。適切な条件判定を行うことで、型に応じた安全な処理を記述できます。

ただし、ユーザー定義型ガードでは、型述語と実際の検証処理が一致していることを開発者自身が保証する必要があります。型ガードを実装する際は、実行時の値を正しく検証できているか確認することが重要です。

組み込みの型ガード3選(typeof / instanceof / in)

TypeScriptにはJavaScriptの標準構文をそのまま型ガードとして認識する仕組みが備わっています。代表的な3つの手法を確認しましょう。

1. typeof 演算子:プリミティブ型の判定

string、number、boolean、symbol などのプリミティブ型を判定する場合は、typeof 演算子を使用します。

function formatInput(input: string | number | boolean): string {
if (typeof input === "string") {
return input.trim();
}
if (typeof input === "number") {
return input.toLocaleString("ja-JP");
}
return input ? "はい" : "いいえ";
}
console.log(formatInput(" TypeScript ")); // 出力: TypeScript
console.log(formatInput(1234567)); // 出力: 1,234,567
console.log(formatInput(true)); // 出力: はい

typeof input === "string" の評価を行うだけで、if 文のブロック内では自動的に string 型へ絞り込まれます。

なお、JavaScriptでは typeof null の結果が "object" になるという仕様上の注意点があります。オブジェクト型と null を区別する場合は、value !== null などの条件も組み合わせて判定しましょう。

2. instanceof 演算子:クラスインスタンスの判定

クラスのインスタンスや標準オブジェクト(Date や Error など)を判定する場合は、instanceof 演算子が有効です。

class ApiSuccess {
constructor(public data: string[]) {}
}
class ApiError {
constructor(public message: string, public statusCode: number) {}
}
type ApiResponse = ApiSuccess | ApiError;
function handleResponse(response: ApiResponse) {
if (response instanceof ApiSuccess) {
console.log(`取得件数: ${response.data.length} 件`);
} else if (response instanceof ApiError) {
console.error(`エラー (${response.statusCode}): ${response.message}`);
}
}
handleResponse(new ApiSuccess(["item1", "item2"])); // 出力: 取得件数: 2 件
handleResponse(new ApiError("Not Found", 404)); // 出力: エラー (404): Not Found

instanceof はプロトタイプチェーンを辿って検証するため、クラスベースの設計と非常に相性が良い手法です。

3. in 演算子:プロパティの有無による判定

TypeScriptのインターフェースやオブジェクト型はコンパイル後に消去されるため、instanceof を使用できません。このようなプレーンオブジェクトを判定する際は in 演算子を使います。

interface Cat {
name: string;
meow: () => void;
}
interface Dog {
name: string;
bark: () => void;
}
type Pet = Cat | Dog;
function playWithPet(pet: Pet) {
if ("meow" in pet) {
// pet は Cat 型に絞り込まれる
pet.meow();
} else {
// pet は Dog 型に絞り込まれる
pet.bark();
}
}

対象のオブジェクトに特定のプロパティが存在するかどうかを調べることで、該当する型に安全に絞り込むことができます。

なお、in 演算子はオブジェクト自身のプロパティだけでなく、プロトタイプチェーン上に存在するプロパティも判定対象とします。また、対象のプロパティがオプショナルとして定義されている場合は、判定結果によって必ずしも型が一意に絞り込まれるわけではないため注意しましょう。

ユーザー定義型ガード(User-Defined Type Guard)

組み込みの演算子だけでは、複雑なオブジェクト構造の検証や、APIAPI [エーピーアイ]Application Programming Interface。異なるソフトウェア間の接続口レスポンスのバリデーションに対応しきれないケースがあります。そのような場合に利用するのが ユーザー定義型ガード です。

is を使った型述語(Type Predicate)

ユーザー定義型ガードを作成するには、戻り値の型として 型述語(Type Predicate) である arg is Type を指定します。

interface UserProfile {
id: string;
name: string;
email: string;
}
// ユーザー定義型ガード関数
function isUserProfile(value: unknown): value is UserProfile {
if (typeof value !== "object" || value === null) {
return false;
}
const candidate = value as Record<string, unknown>;
return (
typeof candidate.id === "string" &&
typeof candidate.name === "string" &&
typeof candidate.email === "string"
);
}

この関数が true を返すと、呼び出し元のスコープ内で引数 value の型が unknown から UserProfile に絞り込まれます。なお、型述語は false を返した場合にも型の絞り込みに影響するため、対象の型に該当するかどうかを正しく判定できる実装にする必要があります。

function processUserData(data: unknown) {
if (isUserProfile(data)) {
// data は安全に UserProfile 型として扱える
console.log(`ID: ${data.id}, Name: ${data.name}`);
} else {
console.warn("不正なデータ形式です");
}
}

配列の filter と組み合わせた活用法

実務でよく遭遇するのが、配列から null や undefined を取り除きたいパターンです。

単純な filter(Boolean) では、TypeScriptの型推論上で null | undefined が除外されない場合があります。また、Boolean による判定では空文字列や数値の 0 なども除外されるため、注意が必要です。

なお、TypeScript 5.5以降では、item => item !== null && item !== undefined のような条件式から型述語が自動推論されるため、明示的な型ガード関数を書かなくても型を絞り込めるケースがあります。

独自の検証処理を共通化したい場合や、複数の箇所で再利用したい場合には、ユーザー定義型ガードが有効です。

function isNonNullable<T>(item: T): item is NonNullable<T> {
return item !== null && item !== undefined;
}
const rawList: (string | null | undefined)[] = ["React", null, "Vue", undefined, "Astro"];
// NonNullable型ガードを使用
const cleanList: string[] = rawList.filter(isNonNullable);
console.log(cleanList); // 出力: [ 'React', 'Vue', 'Astro' ]

型引数 T と組み合わせることで、汎用的に使い回せるユーティリティ関数として重宝します。

アサーション関数(asserts)によるバリデーション

TypeScript 3.7から導入された アサーション関数(Assertion Functions) は、条件を満たさない場合に例外をスローすることで、以降の処理全体で変数の型を絞り込む機能です。

asserts を使ったアサーション関数の基本構文

関数の戻り値に asserts condition または asserts arg is Type を指定します。

function assertIsString(val: unknown): asserts val is string {
if (typeof val !== "string") {
throw new TypeError(`期待された型は string ですが、${typeof val} が渡されました`);
}
}
function handleInput(input: unknown) {
// 検証に失敗すると例外がスローされる
assertIsString(input);
// これ以降の行では input は string 型として確定する
console.log(input.toUpperCase());
}
handleInput("hello world"); // 出力: HELLO WORLD

if 文で何重にもネストさせたくない場合や、関数の冒頭で事前条件(ガード節)を記述したい場合に最適です。

存在チェック用のアサーション関数

値が null や undefined でないことを保証するアサーション関数も頻繁に利用されます。

function assertDefined<T>(val: T, message?: string): asserts val is NonNullable<T> {
if (val === null || val === undefined) {
throw new Error(message ?? "値が存在しません");
}
}
const config: { apiKey?: string } = { apiKey: "secret_token_123" };
assertDefined(config.apiKey, "APIキーが設定されていません");
// 以降、config.apiKey は確実に string 型となる
console.log(config.apiKey.length);

型ガードを使う際の注意点とベストプラクティス

型ガードは非常に強力ですが、正しく理解していないとバグの原因になることもあります。

1. 「ウソの型ガード」に気をつける

ユーザー定義型ガードの戻り値型 value is Type は、コンパイラに対して「この関数が true を返したら、この変数は Type 型であると信じてくれ」と伝える宣言です。

しかし、 関数の実装が誤っていてもコンパイラはそれを検知できません 。

// 危険な例: 実装と型述語が食い違っている
function isDangerouslyString(val: unknown): val is string {
// 常に true を返してしまう重大なバグ
return true;
}
const num: unknown = 123;
if (isDangerouslyString(num)) {
// コンパイルは通るが、実行時に TypeError: num.toUpperCase is not a function が発生!
console.log(num.toUpperCase());
}

型述語を使う際は、関数の内部で確実にプロパティの型や値の存在を網羅的に検証しましょう。

2. 判別可能なユニオン型(Discriminated Union)を優先する

自前で型ガード関数を書く前に、データモデルの設計段階で タグ(判別用プロパティ) を持たせられないかを検討することをおすすめします。

type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
// 自動的に circle 型に絞り込まれる
return Math.PI * shape.radius ** 2;
case "square":
// 自動的に square 型に絞り込まれる
return shape.sideLength ** 2;
}
}

判別用プロパティ(kind など)を用意しておけば、特別な関数を書かなくても switch 文や if 文だけで安全に型が絞り込まれます。

3. 外部APIの検証にはバリデーションライブラリも検討する

APIのレスポンスなど、ネストが深く巨大なオブジェクトを型ガードで手書き検証するのはメンテナンスコストが高くなります。

そうした場合は、 Zod などのスキーマ検証ライブラリを採用すると効果的です。スキーマを定義することで実行時のデータ検証ができるほか、z.infer を使ってスキーマからTypeScriptの型を推論できます。これにより、バリデーションと型定義を一元管理でき、二重管理による不整合を防ぎやすくなります。

まとめ

TypeScriptの型ガード(Type Guard)は、実行時の安全性と静的型チェックの利便性を両立するための基本かつ最重要機能です。

  • typeof: プリミティブ型(string, number など)の判定に使う
  • instanceof: クラスのインスタンスやエラーオブジェクトの判定に使う
  • in: プレーンオブジェクトのプロパティ存在確認に使う
  • is(ユーザー定義型ガード): 複雑なデータ構造や配列フィルタリングでの型絞り込みに使う
  • asserts(アサーション関数): ガード節で例外を投げて以降の型を確定させる

まずは組み込みの typeof や instanceof から活用し、必要に応じてユーザー定義型ガードやアサーション関数を取り入れて、型安全で保守しやすいコードを構築していきましょう。

あわせて読みたい関連記事

型安全な開発やJavaScript/TypeScriptの基礎知識をさらに深めたい方は、以下の記事もあわせてご覧ください。

【JavaScript】var, let, constの違いと使い分けを初心者向けに徹底解説
ITナレッジライフ

【JavaScript】var, let, constの違いと使い分けを初心者向けに徹底解説

JavaScriptの変数宣言(var, let, const)の違いを比較表とコード例で分かりやすく解説。スコープ、再宣言、再代入のルールに加え、「巻き上げ」やTDZ、よくあるバグ例まで詳しく説明します。

コンパイル言語とスクリプト言語の違いとは?特徴・メリット・使い分けを徹底解説
ITナレッジライフ

コンパイル言語とスクリプト言語の違いとは?特徴・メリット・使い分けを徹底解説

コンパイル言語とスクリプト言語の違いを初心者向けに分かりやすく解説。実行の仕組みや速度、開発のしやすさの違いから、Java・Python・JavaScriptなど主要言語の分類、用途別の使い分けまで丁寧に紹介します。

npmとYarn、開発者が知るべき違いとは?
ITナレッジライフ

npmとYarn、開発者が知るべき違いとは?

npmとYarn、JavaScript開発者にとって欠かせないパッケージマネージャーですが、結局どちらを使えばいいのか、そのnpm yarn 違いは何なのか、迷うことはありませんか?本記事では、両者の基本的な役割からインストール速度、依存関係の解決、セキュリティ機能、CLIコマンドまで、具体的な違いを徹底比較します。


以上で本記事の解説を終わります。
よいITライフを!
ITナレッジライフ 運営者
この記事を書いた人

Z (ITナレッジライフ)

現役のITエンジニア。Linux、プログラミング、IT用語など、日々の業務で得た「痒いところに手が届く」技術情報を発信しています。

プロフィールと編集ポリシーを見る →

ITエンジニアにお勧めの本 ↗

おすすめ

エンジニアが知っておきたい思考の整理術 複雑な情報を【理解する】【伝える】テクニック

難易度
実用性
図解

思考の整理というテーマを、実務ですぐに使える具体的な「技術」として落とし込んだ一冊です。

人気記事


記事を評価

Thanks!
目次
Scroll to Top