この投稿はTypeScriptで(string | undefined)[]のようなstringとundefinedが入る配列からundefinedを取り除く処理をfilterメソッドで書くとき、filterメソッドが返す型をstring[]にする方法を紹介します。
問題点
次のようなstringとundefinedが入り混じった配列があり、
const items: (string | undefined)[] = ['a', undefined, 'b', undefined, 'c']
ここからundefinedだけを取り除きstringだけの配列を作りたいとします。
Array.prototype.filterを使うと、1行でその処理が実装できるのはご存知かと思います:
const stringItems = items.filter(item => item !== undefined)
// or
const stringItems = items.filter(item => typeof item === 'string')
処理自体はこれでいいのですが、問題点が1つあります。それは、stringItemsの型がstring[]にならず、元配列の(string | undefined)[]のままになるという点です。このせいで、stringItemsをstring[]だと思って処理しようとするコードはコンパイルエラーになってしまいます:
[Playground Link](https://www.staging-typescript.org/play?#code/MYewdgzgLgBAllApgWwgLhgCmgJzmAcxgB8YBXMAE0QDN9FKBKAbQF0YBeGZgcgEMeAGnJVa9SsJ4AjISOp0wDScB6sAUGtCRYufAQCSSVJ3hGIAOjoAbJDkyYEKRpwB8MKAE8ADohA1TKJxcPLqEPIwaWtDkXj44wHwQiADKUHiEECahBmbmyHxeDkauAcjmUCAAqrGIOADCiYiYjBEaGkA)
解決策: ユーザ定義タイプガードを使う
filterの戻り値をstring[]に型付けするにはどうしたらいいのでしょうか?
解決策のひとつは、filterに渡す関数をユーザ定義タイプガード関数にすることです。普通の関数との書き方の違いは、戻り値の型をbooleanではなく、引数名 is 型にする点です。
今回の例では型は string[] に揃えるので typeof item == 'string' の方法を採用します。
// 普通の関数
const f1 = (item): boolean => typeof item == 'string'
// ユーザ定義タイプガード関数
const f2 = (item): item is string => typeof item == 'string'
ユーザ定義タイプガード関数の詳細は他ドキュメントをご参照ください。
このユーザ定義タイプガード関数をfilterに渡してあげると、filterはstring[]型を返すものとコンパイラに解釈させることができます:
const stringItems = items.filter((item): item is string => typeof item == 'string')
これにより、先程コンパイルエラーになっていたコードの問題も解決されます:

Playground Link
おまけ: 「取り除く」という意味合いにする
上記の解決策で問題自体は解消しましたが、filter(item => typeof item == 'string')の部分が「undefinedを取り除く」というより「stringに絞り込む」という意味合いのコードになっているので、おまけとして「取り除く」という意味になるコードも考えてみたいと思います。
undefinedを取り除くということは、string | undefinedならstringに、string | number | undefinedならstring | numberになるべきです。そういう型のマッピングをするのに便利なのがExclude<T,U>型です。
これを使って「取り除く」路線で実装したコードが次になります:
const stringItems = items.filter(
(item): item is Exclude<typeof item, undefined> => item !== undefined
)
ちなみにこの実装であれば、itemsが(string | undefined)[]から、(string | number | undefined)[]になったとき、filterが返す型もそれに追従して、(string | number)[]になるといった仕掛けになります:
const items: (string | number | undefined)[] = ['a', undefined, 'b', undefined, 'c']
const stringOrNumbers: (string | number)[] = items.filter(
(item): item is Exclude<typeof item, undefined> => item !== undefined
)
最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローしてもらえると嬉しいです
→Twitter@suin