Back

TypeScript 5.5の目玉機能: 型ガード自動推論を完全解説

TypeScript 5.5の目玉機能: 型ガード自動推論を完全解説

TypeScript書いてる人なら、こういうコード何回も書いたことあるよね:

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

あのvalue is stringってやつ、Type Predicateっていうんだけど、今までは型を絞り込む関数作るたびにいちいち書かないといけなかった。

TypeScript 5.5で全部変わった。

Inferred Type Predicatesって機能が入って、TypeScriptが関数のコード見て勝手にtype predicateを作ってくれるようになった。もう手で書かなくていい。書き忘れもない。コンパイラがやってくれる。

ちょっとした便利機能じゃないよ。型安全なコードの書き方そのものが変わる話。何がどう変わったか、詳しく見ていこう。

何が問題だったか: type predicate書くの面倒だった

TypeScript 5.5より前は、型の絞り込みって関数の中でしか効かなかった。別関数に切り出した瞬間、型情報が消えるんだよね:

// これは効く - インラインで型絞り込み const values: (string | number)[] = ['a', 1, 'b', 2]; const strings = values.filter(value => typeof value === 'string'); // TypeScript 5.4: stringsは (string | number)[] ❌ // TypeScript 5.5: stringsは string[] ✅

5.4までは、filterで明らかにstringだけ残してるのに、TypeScriptがそれ分かんなかった。結果が(string | number)[]になって、まあ間違ってはないけど使えない型だった。

だから明示的にtype predicate書く必要があった:

function isString(value: string | number): value is string { return typeof value === 'string'; } const strings = values.filter(isString); // stringsは string[] ✅

単純な例ならいいけど、実際のプロジェクトだと:

  1. type predicate書き忘れてなんで型が絞られないんだ?ってなる
  2. 嘘ついてもエラーにならない - value is stringって書いて実際はnumberチェックしても通る
  3. ボイラープレート爆発 - type guard関数が何十個もあると全部書かないといけない

解決策: TypeScript 5.5が勝手に推論してくれる

5.5からは、コンパイラが関数のコード見て、条件満たしてたらtype predicateを自動で作る:

  1. 戻り値の型やtype predicateを明示してない
  2. return文が1つ(か、複数でも同じロジック)
  3. パラメータをいじってない
  4. 型を絞り込むboolean式を返してる

実際に見ると:

// TypeScript 5.5 - これだけでOK! function isString(value: unknown) { return typeof value === 'string'; } // TypeScriptが勝手に推論: (value: unknown) => value is string const values: unknown[] = ['hello', 42, 'world', null]; const strings = values.filter(isString); // stringsは string[] ✅

コンパイラがreturn文のtypeof value === 'string'を見て、value is stringってtype predicateを自動で付けてくれる。

いつ推論される?

TypeScript 5.5の推論は魔法じゃなくてルールがある。これ知っとくと新機能うまく使える。

ルール1: booleanを返す

type predicateはboolean返す関数にしか意味ない:

// ✅ 推論される function isNumber(x: unknown) { return typeof x === 'number'; } // ❌ ダメ - 値を返してる function getNumber(x: unknown) { return typeof x === 'number' ? x : null; }

ルール2: 戻り値の型を書かない

戻り値の型を明示すると、TypeScriptはそれを尊重して推論しない:

// ❌ 推論されない - booleanって書いちゃったから function isString(value: unknown): boolean { return typeof value === 'string'; } // ✅ 推論される - 何も書いてないから function isString(value: unknown) { return typeof value === 'string'; }

推論オフにしたい時用に、わざとこういう仕様になってる。

ルール3: パラメータいじらない

パラメータを変更すると推論が切れる:

// ❌ 推論されない - 配列にpushしてる function isNonEmptyArray(arr: unknown[]) { arr.push('something'); // 変更! return arr.length > 0; } // ✅ 推論される - 変更なし function isNonEmptyArray(arr: unknown[]) { return arr.length > 0; }

ルール4: trueが本当に型を絞る意味

trueを返す時、それが本当に型を絞り込むことを意味してないといけない:

// ✅ 推論される: trueならstring function isString(x: unknown) { return typeof x === 'string'; } // ⚠️ 推論はされるけどロジック注意 function isNotNull(x: string | null) { return x !== null; } // 推論: x is string (trueの時)

ルール5: 配列メソッドが本命

.filter()がこの機能の真骨頂。フィルター結果の型がちゃんと絞られる:

const mixed: (string | number | null)[] = ['a', 1, null, 'b', 2]; // 5.5前: (string | number | null)[] // 5.5後: (string | number)[] const nonNull = mixed.filter(x => x !== null); // 5.5前: (string | number | null)[] // 5.5後: string[] const strings = mixed.filter(x => typeof x === 'string'); // チェーンもいける! const upperStrings = mixed .filter(x => typeof x === 'string') .map(s => s.toUpperCase()); // sはstringで推論される

実践例: Before & After

例1: オプショナルプロパティのフィルタリング

interface User { id: string; email?: string; phone?: string; } const users: User[] = [ { id: '1', email: '[email protected]' }, { id: '2', phone: '555-1234' }, { id: '3', email: '[email protected]', phone: '555-5678' }, ]; // 5.5前: ヘルパー関数が必要だった function hasEmail(user: User): user is User & { email: string } { return user.email !== undefined; } // 5.5後: 普通にfilter書くだけ const usersWithEmail = users.filter(u => u.email !== undefined); // 型: (User & { email: string })[] usersWithEmail.forEach(u => { console.log(u.email.toUpperCase()); // エラーなし!emailはstring });

例2: ユニオン型の分岐

type Result<T> = | { success: true; data: T } | { success: false; error: string }; const results: Result<number>[] = [ { success: true, data: 42 }, { success: false, error: 'Failed' }, { success: true, data: 100 }, ]; // 5.5前 function isSuccess<T>(result: Result<T>): result is { success: true; data: T } { return result.success; } const successResults = results.filter(isSuccess); // 5.5後 - 普通に書くだけ const successResults = results.filter(r => r.success); // 型: { success: true; data: number }[] const sum = successResults.reduce((acc, r) => acc + r.data, 0); // 動く!

例3: APIレスポンスの検証

interface ApiResponse { status: number; data?: { items: string[]; }; } const responses: ApiResponse[] = await fetchMultipleEndpoints(); // 5.5前: predicate書かないといけなかった function hasData(r: ApiResponse): r is ApiResponse & { data: { items: string[] } } { return r.status === 200 && r.data !== undefined; } // 5.5後: 普通にフィルタリング const validResponses = responses.filter( r => r.status === 200 && r.data !== undefined ); // dataがちゃんとある型に絞られる const allItems = validResponses.flatMap(r => r.data.items); // ✅ エラーなし

注意点

注意1: truthyチェックは効かないことがある

const values: (string | null | undefined)[] = ['a', null, 'b', undefined]; // ❌ これは効かない const truthy = values.filter(x => x); // 型: (string | null | undefined)[] // ✅ 明示的にチェック const defined = values.filter(x => x !== null && x !== undefined); // 型: string[]

xがtruthyでも型が確定しない。空文字はfalsyだけどstringだから。

注意2: Booleanは効かない

const values: (string | null)[] = ['a', null, 'b']; // ❌ 効かない - Booleanはただの関数扱い const filtered = values.filter(Boolean); // 型: (string | null)[] // ✅ アロー関数で const filtered = values.filter(x => x !== null); // 型: string[]

Booleanが型システム上(value?: unknown) => booleanとして定義されてるから。

注意3: 複雑な条件は効かないかも

function isSpecialString(x: unknown) { if (typeof x !== 'string') return false; if (x.length < 5) return false; if (!x.startsWith('prefix')) return false; return true; } // 複雑なのは推論されないかも

複雑なバリデーションは今まで通りtype predicate書いた方がいい。

パフォーマンス大丈夫?

コンパイル時間増えるか気になると思うけど、ほぼ増えない。

TypeScriptは元々関数内の型絞り込みのためにコード解析してる。戻り値のtype predicate推論はその解析結果を使い回すだけ。

実際のテストでコンパイル時間の差は測定誤差レベル。

アップグレード手順

ステップ1: TypeScriptアップデート

npm install [email protected] --save-dev # または yarn add [email protected] --dev # または pnpm add [email protected] --save-dev

ステップ2: 不要なtype predicate削除

今はTypeScriptが推論するやつは消せる:

// 前: 明示的に書いてた function isNumber(x: unknown): x is number { return typeof x === 'number'; } // 後: 推論に任せる function isNumber(x: unknown) { return typeof x === 'number'; }

ただし複雑なロジックやpublic APIは残しておいた方がいい。 ドキュメントとしても役立つから。

ステップ3: filter()の使用箇所をチェック

ここが一番効果出る:

  • インラインpredicateで使ってるfilter()
  • フィルタリング用のヘルパー関数

かなりシンプルにできるはず。

まとめ

TypeScript 5.5のInferred Type Predicatesはマジで便利:

  1. ボイラープレート激減 - value is Typeいちいち書かなくていい
  2. ミスなくなる - コンパイラが作るから嘘つけない
  3. filter()がやっとまともに動く - フィルター結果の型が期待通りになる

アップグレード簡単、パフォーマンス影響なし、効果はすぐ出る。まだ5.5じゃないなら、この機能だけでも上げる価値ある。

もうvalue is stringを100回書く必要ない。TypeScriptがやってくれる。

typescriptjavascripttype-safetyprogrammingweb-developmenttype-guardstypescript-5