404 Not Found

404 Not Found


nginx

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 typeofinstanceof の違いは何ですか?
A typeof は、値の「プリミティブ型」(文字列/数値/ブール値/undefined/オブジェクト/関数)をチェックして文字列を返します。プリミティブ型のチェックに適しています。instanceofは、値が特定のクラスのインスタンスであるかどうかをチェックします。これは参照型(DateError、およびカスタムクラスなど)のチェックに適しています。この2つは互いに補完し合うものであり、排他的ではありません。
Q カスタム型ガードはパフォーマンスにどのような影響を与えますか?
A 影響はありません。型ガードはコンパイル時のみに使用されます。コンパイルされたJavaScriptは、通常のif文とプロパティチェックで構成されています。型述語(x is T)やアサーション関数(asserts x is T)は実行時には存在しないため、オーバーヘッドはゼロです。
Q なぜ in 演算子は型の絞り込みを許可するのですか?
A TypeScript は、オブジェクトが特定のプロパティを持つ場合、そのオブジェクトはそのプロパティを含むインターフェースに属しているに違いないことを認識しているからです。"swim" in animalがtrueの場合、animalswimを含むインターフェースを実装していなければなりません。これは、実行時の型情報を必要としない論理的な推論です。
Q いつカスタム型ガードを記述する必要があるのでしょうか?
A 組み込みの型絞り込みメソッド(typeof、instanceof、in、===)では型を区別できない場合です。最も一般的なシナリオは、インターフェースの区別です。インターフェースは実行時には存在しないため、instanceof を使ってチェックすることはできません。その代わりに、in を使って識別可能なプロパティをチェックするか、カスタム型ガードを記述する必要があります。

📖 まとめ

📝 練習問題

  1. 基本問題(難易度 ⭐):関数 doubleOrRepeat(value: string | number) を記述してください。引数 value が数値の場合は 2 を掛け、文字列の場合は自分自身と 1 回連結します(例:「hi」→「hihi」)。入力の制限には typeof を使用してください。
  2. 上級問題(難易度 ⭐⭐):2つのインターフェース、AdminhasPermissionメソッドを持つ)とGuestrequestAccessメソッドを持つ)を定義し、区別可能な属性roleを用いてそれらを精緻化してください。役割に応じて異なるメソッドを呼び出す関数を作成してください。
  3. 課題(難易度:⭐⭐⭐):カスタム型ガード isNonNull<T>(value: T | null | undefined): value is NonNullable<T> を記述し、それを filterNonNull<T>(arr: (T | null | undefined)[]): T[] 関数内で使用して、null および undefined の値をフィルタリングし、空でない配列を返すようにしてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%