💂

型ガードを使えばfilterも変わる?TypeScriptでの型絞り込みの真実

2024/11/17に公開2

はじめに

TypeScriptでは、ユーザー定義の型ガードを使うことで、コンパイラが「型が特定の条件に合致するか」を理解し、その結果として型を絞り込むことができます。しかし、filter関数を使った場合は型の絞り込みが自動的には適用されません。これは、filter関数が内部的に「ユーザー定義の型ガード」として認識されないためです。

1. ユーザー定義の型ガード

TypeScriptの型ガードは、ある関数が返す値によって型を絞り込む機能です。型ガード関数は特定の条件を満たすかどうかをbooleanで返すだけでなく、value is Typeという返り値の型注釈を使って、TypeScriptに「ある値が特定の型に属している」と教えることができます。

例えば、以下のようにisStringという型ガード関数を定義します:

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

この型ガード関数は、valuestringであるかどうかを確認します。また、value is stringという型注釈により、TypeScriptはisStringtrueを返した場合にvaluestring型として認識することができます。

2. filter関数と型ガード

次に、filter関数とユーザー定義の型ガードを比較してみます。

型ガードを使った場合

const items: (string | number)[] = [1, "hello", 2, "world"];

const strings = items.filter(isString);
// stringsの型はstring[]になります

ここで、filterisStringという型ガードを渡すと、TypeScriptはitems.filter(isString)の結果がstring[]になることを理解します。これは、isStringがユーザー定義の型ガードであり、filterがその戻り値の型を利用して要素の型を絞り込むからです。

filter関数内での型チェックのみの場合

一方で、filter内で直接条件を書くと、型ガードが適用されません。

const strings = items.filter(item => typeof item === "string");
// stringsの型は(string | number)[]のままです

この場合、typeof item === "string"というチェックは型ガードではなく単なる条件式です。このため、TypeScriptはこのfilterの結果がstring[]であると認識できず、元の配列の型(string | number)[]を維持します。

なぜ型ガードでないと絞り込めないのか?

TypeScriptは、通常の条件式を型の絞り込み条件として自動的に利用できません。これは、条件式が型のチェックを目的としていると保証できないためです。しかし、ユーザー定義の型ガードは、明示的にvalue is Typeというシグネチャを持ち、「この関数がtrueを返す場合、型は特定のもの」とコンパイラに知らせるため、信頼して型を絞り込むことができます。

参考文献

https://zenn.dev/kimuson/articles/filter_safety_type_guard
https://typescript-jp.gitbook.io/deep-dive/type-system/typeguard#yznotype-guard
https://zenn.dev/ubie_dev/articles/ts-infer-type-predicates
https://github.com/microsoft/TypeScript/pull/57465

Discussion

YuneKichiYuneKichi

TypeScript 5.5で対応されたと思っていましたが、違いましたっけ。

shigeshige

ご指摘いただきありがとうございます。
正直なところ、私自身もTypeScript 5.5における型述語の自動推論が実装されたことを把握しておらず、結果的に古い情報を記事に掲載してしまいました。
今後、この記事を読んでくださる方に古い情報を届けないよう、該当箇所に最新の内容を追記し、更新いたします。
引き続き、貴重なご意見をいただけますと幸いです!