型ガードを使えばfilterも変わる?TypeScriptでの型絞り込みの真実
はじめに
TypeScriptでは、ユーザー定義の型ガードを使うことで、コンパイラが「型が特定の条件に合致するか」を理解し、その結果として型を絞り込むことができます。しかし、filter
関数を使った場合は型の絞り込みが自動的には適用されません。これは、filter
関数が内部的に「ユーザー定義の型ガード」として認識されないためです。
1. ユーザー定義の型ガード
TypeScriptの型ガードは、ある関数が返す値によって型を絞り込む機能です。型ガード関数は特定の条件を満たすかどうかをboolean
で返すだけでなく、value is Type
という返り値の型注釈を使って、TypeScriptに「ある値が特定の型に属している」と教えることができます。
例えば、以下のようにisString
という型ガード関数を定義します:
function isString(value: unknown): value is string {
return typeof value === "string";
}
この型ガード関数は、value
がstring
であるかどうかを確認します。また、value is string
という型注釈により、TypeScriptはisString
がtrue
を返した場合にvalue
をstring
型として認識することができます。
filter
関数と型ガード
2. 次に、filter
関数とユーザー定義の型ガードを比較してみます。
型ガードを使った場合
const items: (string | number)[] = [1, "hello", 2, "world"];
const strings = items.filter(isString);
// stringsの型はstring[]になります
ここで、filter
にisString
という型ガードを渡すと、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
を返す場合、型は特定のもの」とコンパイラに知らせるため、信頼して型を絞り込むことができます。
参考文献
Discussion
TypeScript 5.5で対応されたと思っていましたが、違いましたっけ。
ご指摘いただきありがとうございます。
正直なところ、私自身もTypeScript 5.5における型述語の自動推論が実装されたことを把握しておらず、結果的に古い情報を記事に掲載してしまいました。
今後、この記事を読んでくださる方に古い情報を届けないよう、該当箇所に最新の内容を追記し、更新いたします。
引き続き、貴重なご意見をいただけますと幸いです!