TypeScript 5.5 で Array.filter() が型を自動で絞り込んでくれるようになった話

TypeScript 5.5 でコールバック型述語の推論が改善され、filter() の戻り値型が自動で絞り込まれるようになりました。型ガード関数を別に書かなくても済む場面が増えて、かなり楽になりました。

🙌 結論から

TypeScript 5.5 から、Array.filter() のコールバック関数が型絞り込みとして機能するようになりました!

以前は filter(x => x !== null) と書いても戻り値が (T | null)[] のままで、別途型ガード関数を定義しないといけませんでした。

5.5 以降は コールバックの戻り値が boolean であれば型を自動推論してくれるようになったので、余計なコードを書かなくて済む場面がかなり増えています(^_^)

💡 以前はどういう問題があったのか

仕事で TypeScript を書いていると、Array.filter() の型推論がいまいちだと感じる場面がよくありました。

例えば、null を除いた配列が欲しいときに以下のように書いていました。

const items: (string | null)[] = ['a', null, 'b', null, 'c']

// これだと戻り値は (string | null)[] のまま
const filtered = items.filter(x => x !== null)

filtered を使う場所で string として扱いたいのに、型が (string | null)[] のままなのでエラーになります。

対策として、こういう型ガード関数を定義する方法が一般的でした(・_・;)

function isNotNull<T>(value: T | null): value is T {
  return value !== null
}

const filtered = items.filter(isNotNull)
// ✅ filtered の型は string[]

これ自体は問題ないのですが、null チェックのためだけに毎回こういう関数を書くのは少し面倒でした。

🔥 TypeScript 5.5 での変更点

TypeScript 5.5 から、filter() のコールバック関数の型絞り込みが改善されました。

コールバックがシンプルな絞り込み条件の場合、TypeScript が自動的に型述語として解釈してくれます。

const items: (string | null)[] = ['a', null, 'b', null, 'c']

// TypeScript 5.5 以降はこれで型が string[] に絞られる!
const filtered = items.filter(x => x !== null)
//                                  ↑ TS が自動で型述語として推論

isNotNull のような型ガード関数を定義しなくても、直接 x => x !== null と書くだけで型が絞れるようになりました。

同様に、以下のようなケースも対応しています。

const numbers: (number | undefined)[] = [1, undefined, 2, undefined, 3]

// number[] として推論される
const validNumbers = numbers.filter(x => x !== undefined)

// truthy チェックでも有効
const nonEmpty: string[] = ['a', '', 'b'].filter(Boolean)

Boolean を直接渡すケースは以前から一部対応していましたが、5.5 でより広いパターンに対応したイメージです!

😊 実際に使ってみた感想

この変更は個人開発でも仕事でも、体感できるレベルで便利になりました。

API レスポンスの配列に null が混じっているデータを処理するときに、毎回型ガード関数を書く必要がありましたが、それがかなり減りました。

Zod でバリデーションしているコードでも、nullable なフィールドを除外するために isNotNull 系の関数を何度も定義していたのが、シンプルに書けるようになっています(^^)

一つ注意点として、複雑な条件の場合は自動推論が効かないことがあります。

// 複合条件はうまく推論されないケースがある
const filtered = items.filter(x => x !== null && x.length > 0)
// 型は (string | null)[] のままになる場合がある

こういったケースでは引き続き型ガード関数を使う方が確実です。

TypeScript のバージョンアップを追いかけるのは大変ですが、こういう体験レベルで便利になる改善は個人的に本当にうれしいです🎉

🔗 satisfies と組み合わせるとさらに安全になる

filter() の型絞り込みに慣れてから、satisfies 演算子と組み合わせる書き方もよく使うようになりました。

APIレスポンスをバリデーションしてから絞り込む、というよくあるパターンで試してみます。

type Status = 'active' | 'archived' | null

const rawStatuses: Status[] = ['active', null, 'archived', null]

// filter で null を除外しつつ、satisfies で意図した型と一致しているか確認する
const statuses = rawStatuses.filter(x => x !== null) satisfies Exclude<Status, null>[]

filter() の型推論だけだと「絞り込まれた後の型が本当に自分の想定と一致しているか」までは保証してくれないんですが、satisfies を後ろに添えておくと、もし型が意図とズレていたらその場でコンパイルエラーになるので、二重のチェックになります(・∀・)

私はZodのようなバリデーションライブラリと組み合わせるときにもこの書き方を使っていて、「バリデーション済みの生データ」から「アプリで使う整形済みデータ」に変換する境界に filter + satisfies を置くようにしています。

地道な組み合わせですが、実行時のバリデーションと静的な型チェックの両方を通過して初めて安心できる、という感覚が持てるようになりました。

ちなみにこの filter() の型絞り込み自体は、その後のバージョンでも変わらず使える基本の書き方として定着しています。

パパ系Webエンジニア〜愛用ガジェット一覧 🛍️

この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌

🙌 まとめ

TypeScript 5.5 の Array.filter() 型推論改善をまとめると:

  • filter(x => x !== null) だけで戻り値型が自動絞り込みされるようになった
  • 型ガード関数(isNotNull など)を書かなくて済む場面が増えた
  • 複雑な条件の場合は引き続き型ガード関数が安全

型ガード関数は「TypeScript あるある」の定番コードでしたが、シンプルな絞り込みで書かなくて済むのは正直かなりうれしいです!

※ TypeScriptのバージョンによって挙動が異なる場合があります。最新情報は TypeScript公式ドキュメント をご確認ください👀

地道な改善が積み重なって開発体験が良くなっていくのが TypeScript の好きなところだと思っています(。•̀ᴗ-)✧