TypeScript: filter で型が絞れない時は flatMap を使うと良いかも

TypeScript: filter で型が絞れない時は flatMap を使うと良いかも

March 1, 2022

こんな配列があるとして、

const items = ['hello', null, 'world'];

こうしても、残念ながら型はうまくフィルタリングされません。

const filtered = items.filter((item) => item !== null);
// filtered: (string | null)[]

ですので、Type Guard を使ってこうするのが一般的だと思うのですが、

const filtered2 = items.filter((item): item is NonNullable<typeof items[number]> => item !== null);
// filtered2: string[]

記述が長くなるのと、Type Guard は結局アサーションしているのと同じなので、別の書き方はないものかと思うものです。

そんなときに見つけたのがこの書き方。

const filtered3 = items.flatMap((item) => item ?? []);
// filtered3: string[]

ちょっとテクニカルですがアサーション不要で機能していて良いですね。

ちなみに flatMap()map() して flat() しているのと同じなので以下でも同じです。

const filtered4 = items.map((item) => item ?? []).flat();
// filtered4: string[]

TypeScript の型の話からはちょっとずれますが、深さが第二階層以上の配列をフラット化したい場合は flat() と組み合わせて使いましょう。

flatMap() は第二階層以上をフラット化しませんが、 flat() ならば、引数に数値を渡すことで、どの深さまでフラット化するのか指定することができます。

const moreItems = ['hello', null, ['hello', null], [['hello', null]], [[['hello', null]]]];

const filtered5 = moreItems.flat(3).flatMap((item) => item ?? []);
// filtered5: string[]