【TypeScript】型ガードとは?typeof・instanceof・in・isの使い方を解説!

TypeScriptでプログラムを書いていると、「この値が文字列だったら文字列用のメソッドを使いたい」「数値だったら数値用の処理をしたい」といった場面があります。

しかし、変数に複数の型が指定されている場合、TypeScriptはその値がどの型なのかを判断できないため、そのままでは使えないメソッドやプロパティがあります。

このようなときに役立つのが、型ガード(Type Guard)です。

型ガードを使うと、値の型を確認し、その結果に応じてTypeScriptが扱う型を絞り込むことができます。これにより、それぞれの型に応じた処理を安全に記述できるようになります。

しかし、型ガードを初めて学ぶと…

  • 型ガードって何をしているの?
  • typeofinstanceofinはどう使い分けるの?
  • ユーザー定義型ガードのisって何?
  • どのような場面で使うの?

といった疑問が浮かんでくるかもしれません。

この記事では、TypeScriptの型ガードについて、サンプルコードを用いてわかりやすく解説します。

型ガードとは

型ガードは、値の型を確認し、その結果に応じてTypeScriptが扱う型を絞り込む仕組みです。

まず、文字列または数値を受け取る関数を考えてみましょう。

function showValue(value: string | number) {
  console.log(value);
}

引数valueの型はstring | numberです。このように、複数の型のいずれかを表す型を「ユニオン型」といいます。

TypeScriptは、この時点ではどちらの型が入っているかわかりません。

そのため、どちらの型にも共通して存在する操作はできますが、片方だけが持つメソッドを安全に呼び出すことはできません。例えば、以下のサンプルコードのように、文字列専用のtoUpperCase()をそのまま呼び出すことはできません。

function showValue(value: string | number) {
  console.log(value.toUpperCase());
  // エラー:number型にはtoUpperCase()が存在しない
}

そこで、typeofを使って文字列かどうかを確認します。

function showValue(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}

showValue("hello"); // HELLO
showValue(123.456); // 123.46

typeof value === "string"trueの場合、TypeScriptはvaluestring型として扱います。elseの中では文字列ではないため、残るnumber型として扱います。

場所TypeScriptが扱う型使えるメソッドの例
条件分岐の前string | number両方の型に共通する操作
文字列と判定された中stringtoUpperCase()
それ以外の中numbertoFixed()

このように、条件や処理の流れに応じて型をより具体的にすることを、型の絞り込み(Narrowing)といいます。

elseを使わなくても型は絞り込まれる

型の絞り込みは、ifelseをセットで書いたときだけ働くわけではありません。

function formatValue(value: string | number): string {
  if (typeof value === "number") {
    return value.toFixed(2);
  }

  return value.toUpperCase();
}

console.log(formatValue(12.5));   // 12.50
console.log(formatValue("hello")); // HELLO

数値だった場合は最初のreturnで関数の処理が終了します。そのため、その後の処理に到達するvalueは文字列だけです。TypeScriptは、このような処理の流れも考慮して型を絞り込みます。

typeof演算子を使った型ガード

typeof演算子は、値の種類を文字列として取得するJavaScriptの演算子です。基本的な書き方は次のとおりです。

typeof 値

例えば、次のように使用します。

console.log(typeof "hello"); // "string"
console.log(typeof 123);     // "number"
console.log(typeof true);    // "boolean"

typeofで取得できる主な結果は、次のとおりです。

値の例typeofの結果
"hello""string"
123"number"
true"boolean"
undefined"undefined"
123n"bigint"
Symbol("id")"symbol"
() => {}"function"
{ name: "太郎" }"object"
[1, 2, 3]"object"
null"object"

例えば、商品IDとして文字列または数値を受け取る関数を考えてみましょう。数値の場合は数値専用のtoFixed()を使用し、文字列の場合はtoUpperCase()を使用して返します。

function formatProductId(id: string | number): string {
  if (typeof id === "number") {
    return `商品ID:${id.toFixed(0)}`;
  }

  return `商品ID:${id.toUpperCase()}`;
}

console.log(formatProductId(123));   // 商品ID:123
console.log(formatProductId("a01")); // 商品ID:A01

ifの中ではidnumber型に絞り込まれています。ifの外では、数値だった場合はすでに処理が終了しているため、string型として扱われます。

nullと配列には注意する

typeofを使う際に注意したいのが、nullと配列です。

console.log(typeof null);    // "object"
console.log(typeof [1, 2]);  // "object"

nullは歴史的なJavaScriptの仕様により"object"を返します。また、配列も"object"を返すため、typeofだけでは通常のオブジェクトと区別できません。

配列かどうかを確認したい場合は、Array.isArray()を使用します。

function showItems(value: string | string[]) {
  if (Array.isArray(value)) {
    console.log(value.join(", "));
  } else {
    console.log(value.toUpperCase());
  }
}

showItems(["りんご", "みかん"]); // りんご, みかん
showItems("hello");             // HELLO

Array.isArray(value)trueの場合、valueは配列として扱われるため、join()などの配列用メソッドを使用できます。

instanceof演算子を使った型ガード

instanceof演算子は、ある値が特定のクラスのインスタンスかどうかを判定する演算子です。

厳密には、オブジェクトのプロトタイプチェーンを利用して判定しますが、まずは「指定したクラスから作られたインスタンスかどうかを確認するもの」と理解しておけば十分です。

基本的な書き方は次のとおりです。

値 instanceof クラス

例えば、Dateクラスのインスタンスかどうかを確認できます。

const today = new Date();

console.log(today instanceof Date); // true

次の関数では、引数に文字列またはDateオブジェクトを受け取ります。

function showDate(value: string | Date) {
  if (value instanceof Date) {
    console.log(value.getFullYear());
  } else {
    console.log(value.toUpperCase());
  }
}

showDate(new Date("2026-09-07")); // 2026
showDate("today");                // TODAY

value instanceof Datetrueの場合、TypeScriptはvalueDate型として扱います。そのため、getFullYear()などのDate専用メソッドを安全に呼び出せます。

自作クラスでも使用できる

instanceofは、自分で定義したクラスに対しても使用できます。

class Dog {
  bark() {
    console.log("ワン!");
  }
}

class Cat {
  meow() {
    console.log("ニャー!");
  }
}

function speak(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

speak(new Dog()); // ワン!
speak(new Cat()); // ニャー!

上記の例では、Dogのインスタンスだった場合はbark()を呼び出し、それ以外の場合はCat型としてmeow()を呼び出しています。

in演算子を使った型ガード

in演算子は、オブジェクトに指定したプロパティが存在するかどうかを確認する演算子です。

オブジェクト自身が持つプロパティだけでなく、継承したプロパティも判定の対象になります。

基本的な書き方は次のとおりです。

"プロパティ名" in オブジェクト

例えば、次のように使用します。

const user = {
  name: "太郎",
  age: 20,
};

console.log("name" in user);  // true
console.log("email" in user); // false

例えば、犬と猫を表す型を定義します。犬はbarkメソッドを持ち、猫はmeowメソッドを持っています。

type Dog = {
  name: string;
  bark: () => void;
};

type Cat = {
  name: string;
  meow: () => void;
};

const dog: Dog = {
  name: "ポチ",
  bark: () => console.log("ワン!"),
};

const cat: Cat = {
  name: "タマ",
  meow: () => console.log("ニャー!"),
};

この2つの型を受け取る関数では、in演算子を使って処理を分けられます。

function speak(pet: Dog | Cat) {
  if ("bark" in pet) {
    pet.bark();
  } else {
    pet.meow();
  }
}

speak(dog); // ワン!
speak(cat); // ニャー!

"bark" in pettrueの場合、TypeScriptはpetDog型として扱います。falseの場合は、今回の型定義ではCat型として扱われます。

このように、それぞれの型が持つプロパティの違いを利用して型を絞り込めるのが、in演算子の特徴です。

オプショナルプロパティがある場合

プロパティが省略可能な場合は、絞り込みの結果に注意が必要です。

type Dog = {
  bark: () => void;
};

type Cat = {
  meow: () => void;
};

type Human = {
  name: string;
  bark?: () => void;
};

function speak(value: Dog | Cat | Human) {
  if ("bark" in value) {
    // Dog | Human
  } else {
    // Cat | Human
  }
}

Humanbarkはオプショナルプロパティなので、存在する場合も存在しない場合もあります。そのため、in演算子のtrue側とfalse側のどちらにもHumanが残ります。

ユーザー定義型ガードとは

ここまで紹介したtypeofinstanceofなどを使うと、値の型を確認して、TypeScriptが扱う型を絞り込むことができます。

しかし、実際の開発では、同じ型の判定処理を何度も書きたくないことがあります。

そのようなときに、型の判定処理を関数としてまとめ、判定結果に応じて型を絞り込めるようにする仕組みが、ユーザー定義型ガードです。

まずはunknown型を理解する

ユーザー定義型ガードを理解するために、まずunknown型を使った例を見てみましょう。unknownは、どのような値が入るかわからないことを表す型です。

例えば、次の関数は、文字列や数値など、さまざまな値を受け取ることができます。

function showText(value: unknown) {
  console.log(value);
}

showText("hello"); // hello
showText(123);     // 123

unknownは、どのような値が入るかわからないことを表す型です。anyとは異なり、型を確認するまでは文字列専用のメソッドなどを自由に使用できません。そのため、以下のサンプルコードのように、unknown型のままでは、文字列専用のtoUpperCase()を呼び出すことはできません。なぜなら、TypeScriptはvalueが文字列なのか、数値なのか、それ以外の型なのかを判断できないためです。

function showText(value: unknown) {
  console.log(value.toUpperCase());
  // エラー:valueはunknown型
}

そこで、typeofを使って文字列かどうかを確認します。

function showText(value: unknown) {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  }
}

showText("hello"); // HELLO
showText(123);     // 何も表示されない

typeof value === "string"trueの場合、TypeScriptはvaluestring型として扱います。そのため、文字列専用のtoUpperCase()を安全に使用できます。つまり、unknown型の値でも、型ガードによって型を確認すれば、その型に応じた操作ができるようになります。

判定処理を関数に切り出す

次に、文字列かどうかを判定する処理(今までのサンプルコードのtypeof value === "string")を別の関数に切り出してみましょう。

例えば、次のようにisString()という関数を作成します。

function isString(value: unknown): boolean {
  return typeof value === "string";
}

この関数は、引数が文字列であればtrue、文字列でなければfalseを返します。

console.log(isString("hello")); // true
console.log(isString(123)); // false

では、この関数を使ってshowText()を書き換えてみましょう。

function isString(value: unknown): boolean {
  return typeof value === "string";
}

function showText(value: unknown) {
  if (isString(value)) {
    console.log(value.toUpperCase());
    // エラー:valueはunknown型
  }
}

ここで次のような疑問を思うかもしれません。

isString(value)trueなら文字列だとわかるのに、なぜエラーになるの?

理由は、isString()の戻り値が単なるboolean型だからです。booleanは、trueまたはfalseを返すことを表します。しかし、trueを返した場合、引数valuestring型である」という情報までは表していません。

そのため、呼び出し側のTypeScriptは、isString(value)trueになっても、valuestring型として扱うことができません。

このようなときに、型の判定結果をTypeScriptに伝えるために使用するのが、ユーザー定義型ガードです。

なお、TypeScript 5.5以降では、戻り値の型を明示していない単純な判定関数について、型述語が自動的に推論される場合があります。ただし、上記のように戻り値を明示的に: booleanと指定した場合は、型述語の情報は伝わりません。

isを使って型ガードを定義する

ユーザー定義型ガードでは、戻り値の型に引数名 is 型という形式を指定します。

先ほどのisString()を、ユーザー定義型ガードに変更してみましょう。

function isString(value: unknown): value is string {
  return typeof value === "string";
}

変更したのは、戻り値の型です。

// 普通のboolean関数
function isString(value: unknown): boolean

// ユーザー定義型ガード
function isString(value: unknown): value is string

ここで重要なのが、value is stringの部分です。これは、「この関数がtrueを返した場合、引数valuestring型である」とTypeScriptに伝えるための書き方です。つまり、isString()は単にtruefalseを返すだけでなく、その判定結果と引数の型の関係もTypeScriptに伝えます。

書き方TypeScriptに伝える内容
: booleantrueまたはfalseを返す
: value is stringtrueを返したら、valuestring型である

value is stringのような書き方を、型述語(Type Predicate)といいます。

それでは、ユーザー定義型ガードを使ってみましょう。

function isString(value: unknown): value is string {
  return typeof value === "string";
}

function showText(value: unknown) {
  if (isString(value)) {
    console.log(value.toUpperCase());
  } else {
    console.log("文字列ではありません");
  }
}

showText("hello"); // HELLO
showText(123);     // 文字列ではありません

今度は、isString(value)trueになった場合、TypeScriptがvaluestring型として扱うため、toUpperCase()を呼び出せます。

  • valueunknown型の値を受け取る
  • isString(value)で実際に文字列かどうかを判定する
  • trueならTypeScriptがvaluestring型に絞り込む
  • 文字列専用のtoUpperCase()を安全に使用できる

このように、ユーザー定義型ガードを使うことで、型の判定処理を関数にまとめながら、呼び出し側でも型を絞り込めるようになります。

ユーザー定義型ガードの注意点

ユーザー定義型ガードは便利ですが、戻り値にvalue is stringと書いただけで、TypeScriptが関数の中身の正しさを保証してくれるわけではありません。

例えば、次のコードは誤った型ガードです。

function isString(value: unknown): value is string {
  return true;
}

この関数は、どのような値を渡してもtrueを返してしまいます。

console.log(isString("hello")); // true
console.log(isString(123));     // true

しかし、戻り値にはvalue is stringと指定されているため、TypeScriptは「trueなら文字列」と判断します。

const value: unknown = 123;

if (isString(value)) {
  console.log(value.toUpperCase());
  // 実行時エラー:toUpperCase is not a function
}

実際の値は数値なのに、TypeScriptは文字列だと判断してしまうため、実行時エラーが発生します。

つまり、is「この条件を満たしたら、この型として扱ってよい」とTypeScriptに伝える宣言であり、実際の値を自動的に検証するものではありません。

そのため、ユーザー定義型ガードを作成するときは、次のように、実際の判定処理とisで指定する型が一致するように実装することが重要です。

function isString(value: unknown): value is string {
  return typeof value === "string";
}

このように正しく実装することで、型の判定処理を再利用しながら、安全に型を絞り込むことができます。

型ガードの使い分け

型ガードは、確認したい値やオブジェクトの種類によって使い分けます。

方法主な用途使用例
typeof文字列・数値・真偽値などを判定するtypeof value === "string"
instanceofクラスのインスタンスかどうかを判定するvalue instanceof Date
inオブジェクトのプロパティが存在するかを判定する"bark" in pet
Array.isArray()配列かどうかを判定するArray.isArray(value)
ユーザー定義型ガード判定処理を関数にまとめて再利用するvalue is string

例えば、文字列か数値かを判定したい場合はtypeofDateオブジェクトかどうかを判定したい場合はinstanceofを使用します。

オブジェクトのプロパティの有無で区別したい場合はinを使用します。

また、同じ判定処理を複数の場所で使用する場合は、ユーザー定義型ガードとして関数にまとめることで、処理を再利用しながら型を絞り込めます。

【応用】型ガードの判定結果を変数に代入する

TypeScript 4.4以降では、型ガードの判定結果を変数に代入して、その変数を条件分岐に使用できるようになりました。

例えば、次のコードを考えてみましょう。

function showDate(value: string | Date) {
  const isDate = value instanceof Date;

  if (isDate) {
    console.log(value.getFullYear());
  }
}

showDate(new Date("2026-09-07")); // 2026

isDateには、value instanceof Dateの判定結果が格納されています。

const isDate = value instanceof Date;

TypeScriptは、isDatetrueの場合、元のvalueDate型であることを理解できます。そのため、ifの中でgetFullYear()を呼び出せます。

変数に代入するメリット

判定結果を変数に代入すると、条件に意味のある名前を付けられます。

function showMessage(value: string | number) {
  const isText = typeof value === "string";

  if (isText) {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}

showMessage("hello"); // HELLO
showMessage(12.5);    // 12.50

複数の条件を組み合わせる場合にも、処理の意図がわかりやすくなります。

function showName(
  value: { name: string } | null
) {
  const hasName = value !== null;

  if (hasName) {
    console.log(value.name);
  }
}

ただし、どのような変数代入でも無条件に型が絞り込まれるわけではありません。TypeScriptが元の判定との関係を追跡できる必要があります。

本記事のまとめ

この記事では『TypeScriptの型ガード』について説明しました。

型ガードは、値の型を確認し、TypeScriptが扱う型を絞り込む仕組みです。

型ガードを使うことで、ユニオン型やunknown型の値も、型に応じて安全に扱えるようになります。

お読みいただきありがとうございました。

スポンサーリンク