TypeScriptでプログラムを書いていると、「この値が文字列だったら文字列用のメソッドを使いたい」「数値だったら数値用の処理をしたい」といった場面があります。
しかし、変数に複数の型が指定されている場合、TypeScriptはその値がどの型なのかを判断できないため、そのままでは使えないメソッドやプロパティがあります。
このようなときに役立つのが、型ガード(Type Guard)です。
型ガードを使うと、値の型を確認し、その結果に応じてTypeScriptが扱う型を絞り込むことができます。これにより、それぞれの型に応じた処理を安全に記述できるようになります。
しかし、型ガードを初めて学ぶと…
- 型ガードって何をしているの?
typeof・instanceof・inはどう使い分けるの?- ユーザー定義型ガードの
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.46typeof value === "string"がtrueの場合、TypeScriptはvalueをstring型として扱います。elseの中では文字列ではないため、残るnumber型として扱います。
| 場所 | TypeScriptが扱う型 | 使えるメソッドの例 |
|---|---|---|
| 条件分岐の前 | string | number | 両方の型に共通する操作 |
| 文字列と判定された中 | string | toUpperCase() |
| それ以外の中 | number | toFixed() |
このように、条件や処理の流れに応じて型をより具体的にすることを、型の絞り込み(Narrowing)といいます。
elseを使わなくても型は絞り込まれる
型の絞り込みは、ifとelseをセットで書いたときだけ働くわけではありません。
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:A01ifの中ではidがnumber型に絞り込まれています。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"); // HELLOArray.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"); // TODAYvalue instanceof Dateがtrueの場合、TypeScriptはvalueをDate型として扱います。そのため、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 petがtrueの場合、TypeScriptはpetをDog型として扱います。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
}
}Humanのbarkはオプショナルプロパティなので、存在する場合も存在しない場合もあります。そのため、in演算子のtrue側とfalse側のどちらにもHumanが残ります。
ユーザー定義型ガードとは
ここまで紹介したtypeofやinstanceofなどを使うと、値の型を確認して、TypeScriptが扱う型を絞り込むことができます。
しかし、実際の開発では、同じ型の判定処理を何度も書きたくないことがあります。
そのようなときに、型の判定処理を関数としてまとめ、判定結果に応じて型を絞り込めるようにする仕組みが、ユーザー定義型ガードです。
まずはunknown型を理解する
ユーザー定義型ガードを理解するために、まずunknown型を使った例を見てみましょう。unknownは、どのような値が入るかわからないことを表す型です。
例えば、次の関数は、文字列や数値など、さまざまな値を受け取ることができます。
function showText(value: unknown) {
console.log(value);
}
showText("hello"); // hello
showText(123); // 123unknownは、どのような値が入るかわからないことを表す型です。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はvalueをstring型として扱います。そのため、文字列専用の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を返した場合、引数valueはstring型である」という情報までは表していません。
そのため、呼び出し側のTypeScriptは、isString(value)がtrueになっても、valueをstring型として扱うことができません。
このようなときに、型の判定結果を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を返した場合、引数valueはstring型である」とTypeScriptに伝えるための書き方です。つまり、isString()は単にtrueやfalseを返すだけでなく、その判定結果と引数の型の関係もTypeScriptに伝えます。
| 書き方 | TypeScriptに伝える内容 |
|---|---|
: boolean | trueまたはfalseを返す |
: value is string | trueを返したら、valueはstring型である |
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がvalueをstring型として扱うため、toUpperCase()を呼び出せます。
valueにunknown型の値を受け取るisString(value)で実際に文字列かどうかを判定するtrueならTypeScriptがvalueをstring型に絞り込む- 文字列専用の
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 |
例えば、文字列か数値かを判定したい場合はtypeof、Dateオブジェクトかどうかを判定したい場合は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")); // 2026isDateには、value instanceof Dateの判定結果が格納されています。
const isDate = value instanceof Date;TypeScriptは、isDateがtrueの場合、元のvalueがDate型であることを理解できます。そのため、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型の値も、型に応じて安全に扱えるようになります。
お読みいただきありがとうございました。