TypeScript の型ガードと型絞り込み
型絞り込みは、TypeScriptの最も実用的な機能の一つです。これは、広範な型をより正確な範囲に絞り込むことで、特定の型のプロパティやメソッドに安全にアクセスできるようにするものです。
1. 型絞り込みの概要
ユニオン型の変数は、すべてのメンバーに共通するプロパティにしかアクセスできません。型絞り込みを使用すると、特定のコード分岐において型を「絞り込む」ことで、その分岐に固有のメンバーにアクセスできるようになります:
TYPESCRIPT
function process(value: string | number) {
// Before narrowing——value Only shared methods can be used
console.log(value.toString()); // ✅ string and number All of them
// After narrowing——value You can use a specific method
if (typeof value === "string") {
console.log(value.toUpperCase()); // ✅ string Unique Approach
} else {
console.log(value.toFixed(2)); // ✅ number Unique Approach
}
}
TypeScript では、以下の種類の型絞り込みがサポートされています:
| 絞り込み方法 | 適用可能なシナリオ |
|---|---|
typeof |
基本型のチェック |
instanceof |
クラスインスタンスの確認 |
in 演算子 |
そのプロパティは存在しますか? |
| 等価性チェック | ===/!== の比較 |
| カスタム型ガード | 複合型のチェック |
| 絞り込み可能 | 割り当て時に自動的に絞り込む |
2. typeofによる型絞り込み
typeof 主に基本型の区別に用いられる:
(1) 基本的な使い方
TYPESCRIPT
function padLeft(value: string, padding: string | number): string {
if (typeof padding === "number") {
return " ".repeat(padding) + value; // padding narrowed to number
}
return padding + value; // padding narrowed to string
}
console.log(padLeft("hello", 4)); // " hello"
console.log(padLeft("hello", ">>>")); // ">>>hello"
(2) typeof の戻り値
TYPESCRIPT
typeof "hello" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 100n // "bigint"
typeof {} // "object" ⚠️ Including null, Array, Date, etc.
typeof function(){} // "function"
🔥 よくある間違い:
typeof null === "object" は JavaScript の歴史的なバグです。typeof を使って null と他のオブジェクトを区別することはできません。確認するには === null を使用する必要があります。
(3) switch を使った typeof
TYPESCRIPT
function describe(value: string | number | boolean | undefined) {
switch (typeof value) {
case "string":
return `String:${value.toUpperCase()}`;
case "number":
return `Numbers:${value.toFixed(2)}`;
case "boolean":
return `Boolean:${value}`;
case "undefined":
return "Undefined";
}
}
3. instanceof による型絞り込み
instanceof オブジェクトが特定のクラスのインスタンスであるかどうかを確認する――参照型を絞り込むのに適している:
(1) 基本的な使い方
TYPESCRIPT
function formatValue(value: Date | string | Error): string {
if (value instanceof Date) {
return value.toISOString(); // Date Methods
} else if (value instanceof Error) {
return value.message; // Error Properties
} else {
return value.toUpperCase(); // string Methods
}
}
console.log(formatValue(new Date())); // "2024-..."
console.log(formatValue(new Error("Error"))); // "Error"
console.log(formatValue("hello")); // "HELLO"
(2) instanceofの制限事項
instanceof クラスインスタンスでのみ使用可能であり、インターフェースや型エイリアス(これらは実行時には存在しない)では使用できません:
TYPESCRIPT
interface Dog { bark(): void; }
interface Cat { meow(): void; }
// ❌ instanceof Cannot be used for interfaces
// if (pet instanceof Dog) { ... }
// ✅ Do you need to use a custom type guard or in Operator
(3) カスタムクラスと instanceof
TYPESCRIPT
class NetworkError extends Error {
constructor(public statusCode: number) {
super(`Network Error:${statusCode}`);
}
}
class ValidationError extends Error {
constructor(public field: string) {
super(`Validation Error:${field}`);
}
}
function handleError(error: NetworkError | ValidationError): string {
if (error instanceof NetworkError) {
return `HTTP ${error.statusCode} Error`;
} else {
return `Field ${error.field} Invalid`;
}
}
4. in 演算子の適用範囲の絞り込み
in オブジェクトが特定のプロパティを持っているかどうかを確認します。インターフェースと型エイリアスを区別するのに役立ちます:
(1) 基本的な使い方
TYPESCRIPT
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // ✅ Fish Type
} else {
animal.fly(); // ✅ Bird Type
}
}
(2) 区別可能な属性
TYPESCRIPT
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // ✅ Circle 's radius
} else {
return shape.sideLength ** 2; // ✅ Square 's sideLength
}
}
▶ 例:型安全なイベント処理
TYPESCRIPT
interface ClickEvent {
type: "click";
x: number;
y: number;
}
interface KeyEvent {
type: "keydown" | "keyup";
key: string;
ctrlKey: boolean;
}
interface ScrollEvent {
type: "scroll";
scrollTop: number;
scrollLeft: number;
}
type UIEvent = ClickEvent | KeyEvent | ScrollEvent;
function handleEvent(event: UIEvent): string {
switch (event.type) {
case "click":
return `Click here:(${event.x}, ${event.y})`;
case "keydown":
case "keyup":
return `Button:${event.key},Ctrl:${event.ctrlKey}`;
case "scroll":
return `Scroll:top=${event.scrollTop}, left=${event.scrollLeft}`;
}
}
console.log(handleEvent({ type: "click", x: 100, y: 200 }));
console.log(handleEvent({ type: "keydown", key: "Enter", ctrlKey: false }));
console.log(handleEvent({ type: "scroll", scrollTop: 50, scrollLeft: 0 }));
出力:
TEXT
Click here:(100, 200)
Button:Enter,Ctrl:false
Scroll:top=50, left=0
5. カスタム・タイプガード
組み込みの絞り込みメソッドでは不十分な場合、カスタム型のガード関数を記述することができます:
(1) 型述語
TYPESCRIPT
interface Dog {
bark(): void;
breed: string;
}
interface Cat {
meow(): void;
color: string;
}
// Type Predicates:The return value is "Parameter Name is Type"
function isDog(animal: Dog | Cat): animal is Dog {
return "bark" in animal;
}
function interact(animal: Dog | Cat) {
if (isDog(animal)) {
animal.bark(); // ✅ narrowed to Dog
console.log(animal.breed);
} else {
animal.meow(); // ✅ narrowed to Cat
console.log(animal.color);
}
}
(2) アサーション関数
アサーション関数は、条件が満たされない場合に例外をスローします。これは、TypeScript に対して「この行が実行される場合、条件は真でなければならない」と伝えるものです:
TYPESCRIPT
function assertDefined<T>(value: T | undefined | null, message?: string): asserts value is NonNullable<T> {
if (value == null) {
throw new Error(message ?? "The value cannot be null or undefined");
}
}
function processUser(user: User | undefined) {
assertDefined(user, "User does not exist");
// After that user The type has been narrowed down to User(Excludes undefined)
console.log(user.name.toUpperCase()); // ✅ Safety
}
(3) アサーションと型述語
| プロパティ | タイプ述語 x is T |
断言関数 asserts x is T |
|---|---|---|
| 戻り値 | ブール値 | void(条件が満たされない場合は例外をスローする) |
| 使用法 | if (isType(x)) |
assertIsType(x) |
| 型の絞り込み | if分岐内での絞り込み | 呼び出し後の自動絞り込み |
| 適用されるシナリオ | チェック後の分岐処理 | 前提条件のチェック |
6. 割り当ての絞り込み
代入演算では、型の絞り込みも行われます:
TYPESCRIPT
let value: string | number;
value = "hello";
console.log(value.toUpperCase()); // ✅ After assignment, it narrows to string
value = 42;
console.log(value.toFixed(2)); // ✅ After assignment, it narrows to number
(1) 制御フロー解析
TypeScript は、制御フロー全体を通じて変数の型の変化を追跡します:
TYPESCRIPT
function example(x: string | number | boolean) {
// x: string | number | boolean
if (typeof x === "string") {
// x: string
console.log(x.toUpperCase());
} else {
// x: number | boolean
if (typeof x === "number") {
// x: number
console.log(x.toFixed(2));
} else {
// x: boolean
console.log(x);
}
}
}
(2) 範囲の絞り込みと再割り当て
TYPESCRIPT
let value: string | number;
value = "hello";
console.log(value.length); // ✅ string
value = 42;
// console.log(value.length); // ❌ number None length
value = true; // ❌ boolean Not in a composite type
7. 網羅的チェック
switch文やif文が考えられるすべての型を網羅していることを確認してください。完全性を保証するには、never 型を使用してください:
TYPESCRIPT
type Shape = "circle" | "square" | "triangle";
function getIcon(shape: Shape): string {
switch (shape) {
case "circle": return "○";
case "square": return "□";
case "triangle": return "△";
default: {
// If all case It's all taken care of.,shape Here is never
const _exhaustive: never = shape;
return _exhaustive;
}
}
}
// If in the future Shape Added "hexagon" But I didn't add it case
// default Branched _exhaustive Report Type Error
// This is a reminder to fill in any missing information. case
(1) より簡潔で網羅的なチェック
TYPESCRIPT
function assertNever(value: never): never {
throw new Error(`Unprocessed values:${value}`);
}
type Action = "create" | "update" | "delete";
function handleAction(action: Action) {
switch (action) {
case "create": /* ... */ break;
case "update": /* ... */ break;
case "delete": /* ... */ break;
default:
assertNever(action); // If omitted case,A type error will be reported here.
}
}
❓ よくある質問
Q
typeof と instanceof の違いは何ですか?A
typeof は、値の「プリミティブ型」(文字列/数値/ブール値/undefined/オブジェクト/関数)をチェックして文字列を返します。プリミティブ型のチェックに適しています。instanceofは、値が特定のクラスのインスタンスであるかどうかをチェックします。これは参照型(Date、Error、およびカスタムクラスなど)のチェックに適しています。この2つは互いに補完し合うものであり、排他的ではありません。Q カスタム型ガードはパフォーマンスにどのような影響を与えますか?
A 影響はありません。型ガードはコンパイル時のみに使用されます。コンパイルされたJavaScriptは、通常の
if文とプロパティチェックで構成されています。型述語(x is T)やアサーション関数(asserts x is T)は実行時には存在しないため、オーバーヘッドはゼロです。Q なぜ
in 演算子は型の絞り込みを許可するのですか?A TypeScript は、オブジェクトが特定のプロパティを持つ場合、そのオブジェクトはそのプロパティを含むインターフェースに属しているに違いないことを認識しているからです。
"swim" in animalがtrueの場合、animalはswimを含むインターフェースを実装していなければなりません。これは、実行時の型情報を必要としない論理的な推論です。Q いつカスタム型ガードを記述する必要があるのでしょうか?
A 組み込みの型絞り込みメソッド(typeof、instanceof、in、===)では型を区別できない場合です。最も一般的なシナリオは、インターフェースの区別です。インターフェースは実行時には存在しないため、instanceof を使ってチェックすることはできません。その代わりに、in を使って識別可能なプロパティをチェックするか、カスタム型ガードを記述する必要があります。
📖 まとめ
- 型絞り込み(広範な型を特定の範囲に絞り込むプロセス)は、ユニオン型を安全に利用するための鍵となる。
typeofはプリミティブ型であるかどうかを、instanceofはクラスのインスタンスであるかどうかを、inはプロパティが存在するかどうかを確認します- カスタム型ガード(型述語
x is T)は、組み込みのメソッドでは区別できない複雑な型を扱う - アサーション関数 (
asserts x is T) は事前チェックを行い、条件が満たされていない場合は例外をスローします。 - TypeScriptの制御フロー解析は、分岐内での変数の型の変化を自動的に追跡します
- 網羅的なチェックを行う際は
neverタイプを使用し、switchおよびifのステートメントがすべての可能性を網羅していることを確認してください
📝 練習問題
- 基本問題(難易度 ⭐):関数
doubleOrRepeat(value: string | number)を記述してください。引数valueが数値の場合は 2 を掛け、文字列の場合は自分自身と 1 回連結します(例:「hi」→「hihi」)。入力の制限にはtypeofを使用してください。 - 上級問題(難易度 ⭐⭐):2つのインターフェース、
Admin(hasPermissionメソッドを持つ)とGuest(requestAccessメソッドを持つ)を定義し、区別可能な属性roleを用いてそれらを精緻化してください。役割に応じて異なるメソッドを呼び出す関数を作成してください。 - 課題(難易度:⭐⭐⭐):カスタム型ガード
isNonNull<T>(value: T | null | undefined): value is NonNullable<T>を記述し、それをfilterNonNull<T>(arr: (T | null | undefined)[]): T[]関数内で使用して、null および undefined の値をフィルタリングし、空でない配列を返すようにしてください。



