mcpField
T
type-narrowing

type-narrowing

github.com/marius-townhouse開発662 回インストール39 週間前に更新
リポジトリ

type-narrowing について

制御フローによって広い型をより具体的な型へ絞り込む TypeScript のスキルです。ユニオン型や null 許容値を扱い、不要な型アサーションをチェックや型ガードに置き換える方法を説明します。

主な機能

  1. 01null と undefined のチェック
  2. 02typeof と instanceof による絞り込み
  3. 03判別可能なユニオン型の処理
  4. 04ユーザー定義型ガードの作成
  5. 05よくある絞り込みの落とし穴の把握

活用シーン

  1. 01undefined の可能性があるエラーの解消
  2. 02ユニオン型の値の処理
  3. 03条件分岐での型アサーションの置き換え
  4. 04null 許容の DOM 要素の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill type-narrowing

インストール後、Claude Code で/type-narrowingを入力して読み込まれたか確認します。

詳細

型の絞り込みを理解する

概要

型の絞り込みとは、制御フローに基づいて TypeScript が型を広いものからより具体的なものへと絞り込むプロセスです。

絞り込みを習得すると、型アサーションを使わずによりすっきりしたコードを書けるようになり、TypeScript がコードのロジックを理解しやすくなります。

このスキルを使う場面

  • Type | null または Type | undefined を扱うとき
  • string | number のようなユニオン型を処理するとき
  • 判別可能なユニオン(タグ付きユニオン)を処理するとき
  • 「undefined の可能性があります」というエラーが発生したとき
  • 条件分岐で型アサーションを避けたいとき

絶対のルール

NEVER use type assertions when narrowing would work.

例外はありません:

  • 「そのほうが簡単だから」という理由では使いません
  • 「すでに確認したから」という理由では使いません
  • 「TypeScript が理解しないから」という理由では使いません

検出:「条件分岐内の型アサーション」という危険信号

if ブロック内で as Type を使っている場合は、代わりに型を絞り込める可能性があります。

// ❌ VIOLATION: Using assertion instead of narrowing
function process(value: string | null) {
  if (value !== null) {
    console.log((value as string).toUpperCase());  // Unnecessary assertion
  }
}

// ✅ CORRECT: TypeScript narrows automatically
function process(value: string | null) {
  if (value !== null) {
    console.log(value.toUpperCase());  // value is string here
    //          ^? (parameter) value: string
  }
}

絞り込みの手法

1. null/undefined のチェック
const el = document.getElementById('foo');
//    ^? const el: HTMLElement | null

if (el) {
  el.innerHTML = 'Hello';
  // ^? const el: HTMLElement
} else {
  el
  // ^? const el: null
}
2. typeof による型ガード
function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return ' '.repeat(padding) + value;
    //                ^? (parameter) padding: number
  }
  return padding + value;
  //     ^? (parameter) padding: string
}
3. instanceof による型ガード
function processDate(input: Date | string) {
  if (input instanceof Date) {
    return input.toISOString();
    //     ^? (parameter) input: Date
  }
  return new Date(input).toISOString();
  //              ^? (parameter) input: string
}
4. プロパティのチェック(in)
interface Bird { fly(): void; }
interface Fish { swim(): void; }

function move(animal: Bird | Fish) {
  if ('fly' in animal) {
    animal.fly();
    // ^? (parameter) animal: Bird
  } else {
    animal.swim();
    // ^? (parameter) animal: Fish
  }
}
5. 判別可能なユニオン(タグ付きユニオン)
interface Circle {
  kind: 'circle';
  radius: number;
}
interface Rectangle {
  kind: 'rectangle';
  width: number;
  height: number;
}
type Shape = Circle | Rectangle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
      //               ^? (parameter) shape: Circle
    case 'rectangle':
      return shape.width * shape.height;
      //     ^? (parameter) shape: Rectangle
  }
}
6. Array.isArray
function process(input: string | string[]) {
  if (Array.isArray(input)) {
    return input.join(', ');
    //     ^? (parameter) input: string[]
  }
  return input;
  //     ^? (parameter) input: string
}
7. 早期の throw/return
function processElement(el: HTMLElement | null) {
  if (!el) {
    throw new Error('Element not found');
  }
  // After the throw, el is narrowed
  el.innerHTML = 'Hello';
  // ^? (parameter) el: HTMLElement
}

ユーザー定義の型ガード

組み込みの絞り込みだけでは不十分な場合:

interface Cat { meow(): void; }
interface Dog { bark(): void; }

// Type predicate: `pet is Cat`
function isCat(pet: Cat | Dog): pet is Cat {
  return 'meow' in pet;
}

function speak(pet: Cat | Dog) {
  if (isCat(pet)) {
    pet.meow();
    // ^? (parameter) pet: Cat
  } else {
    pet.bark();
    // ^? (parameter) pet: Dog
  }
}

絞り込みでよくある落とし穴

typeof null は "object"
function process(value: string | object | null) {
  if (typeof value === 'object') {
    value  // Still includes null!
    // ^? string | object | null -> object | null
  }
}

// Fix: Check null explicitly first
function process(value: string | object | null) {
  if (value === null) return;
  if (typeof value === 'object') {
    value  // Now just object
    // ^? (parameter) value: object
  }
}
偽値
function process(x?: number | string | null) {
  if (!x) {
    x  // Includes 0, "", null, undefined!
    // ^? string | number | null | undefined
  }
}
コールバックでは絞り込みが維持されない
function processLater(value: { name?: string }) {
  if (value.name) {
    setTimeout(() => {
      console.log(value.name.toUpperCase());
      //          ~~~~~~~~~ Object is possibly 'undefined'
    }, 100);
  }
}

// Fix: Capture the narrowed value
function processLater(value: { name?: string }) {
  if (value.name) {
    const name = value.name;  // Capture as const
    setTimeout(() => {
      console.log(name.toUpperCase());  // OK
    }, 100);
  }
}

プレッシャーへの対処プロトコル

1. 「TypeScript がチェックを理解しない」

プレッシャー:「チェックしたのに、TypeScript が型を絞り込まない」

対応: TypeScript が理解できるパターンを使うようにチェックを書き直します。

アクション: 標準的な絞り込みパターンを使います。必要に応じて型ガードを作成します。

2. 「型アサーションのほうが簡単」

プレッシャー:「if 文の代わりに as Type を使えばいい」

対応: 型アサーションは実行時に検証しません。絞り込みなら検証できます。

アクション: チェックを記述します。コードの安全性が高まります。

危険信号 — 立ち止まって再検討する

  • if/switch ブロック内の as Type
  • うまく機能しない絞り込み(パターンを確認してください)
  • null チェックなしの typeof x === 'object'
  • 0 や "" の可能性がある値に対する偽値チェック
  • 適切な null チェックの代わりに ! を使う

よくある正当化(すべて不適切)

言い訳実際のところ
「すでに確認した」TypeScript がチェックを認識しないなら、確認したことにはなりません。
「型アサーションのほうが短い」コードは短ければよいとは限りません。
「TypeScript が間違っている」TS が理解できるパターンを使うようにチェックを書き直します。

クイックリファレンス

使用する型使うもの絞り込まれる型
T | nullif (x) または if (x !== null)T
string | numbertypeof x === 'string'string
Dog | Catx instanceof DogDog
A | B(kind を持つ)switch (x.kind)A または B
複雑なチェックユーザー定義の型ガード独自の型

要点

制御フローを通じて TypeScript に型を絞り込ませましょう。型アサーションで迂回しないでください。

標準的な絞り込みパターンを使い、必要に応じて型ガードを作成します。コールバックの前に値を変数へ格納します。TypeScript の絞り込みは強力です。回避するのではなく、うまく活用する方法を学びましょう。

参考文献

Dan Vanderkam 著「Effective TypeScript」項目 22「型の絞り込みを理解する」に基づいています。

コメント