TypeScript で配列から nullable な要素を除去する
はじめに
この記事は #EveOneZenn (Everyday One Zenn) vol.13 です。
配列から nullable な要素を除去するフィルタを TypeScript で実装する方法をまとめます。
前回:
TypeScript 5.5 でビルトインサポートになります
2024/03/18 追加
2024/06/22 追記
TypeScript 5.5 にて Array.filter
による型の絞り込みがビルトインでサポートされるようになりました。
この記事で紹介している例も、 nonNullable
に相当するユーザー定義の型ガードがなくても期待する型の絞り込みが行えます。
const arr = [0, 1, 'hello', false, null, undefined];
const result = arr.filter((item) => item != null);
// js: [0, 1, 'hello', false]
// ts: (string | number | boolean)[] 🎉
配列から nullable な値を除去する
JavaScript で配列から nullable (undefined
と null
) を除去する方法としては、 Array.prototype.filter
メソッドで要素が nullable でないかチェックするのが一般的かと思います。
const arr = [0, 1, 'hello', false, null, undefined];
arr.filter((item) => item != null);
// js: [0, 1, 'hello', false]
JavaScript ではこの実装で問題ありませんが、 TypeScript で同様のコードを実行すると型情報上に nullable が残ってしまいます。
const arr = [0, 1, 'hello', false, null, undefined];
// ts: (number | string | boolean | null | undefined)[]
arr.filter((item) => item != null);
// js: [0, 1, 'hello', false]
// ts: (number | string | boolean | null | undefined)[]
型情報からも nullable な要素を除去する
Array.prototype.filter
メソッドのビルトイン型からの推論では、型情報から nullable を除去することができませんでした。
そこで、ユーザー定義の Type Guard を使用して型ガードを明示的に定義します。
ユーザー定義の Type Guard は a is B
( a
が変数、 B
が型)で変数 a
の型を B
と定義し直すことができます。
const arr = [0, 1, 'hello', false, null, undefined];
// ts: (number | string | boolean | null | undefined)[]
arr.filter((item): item is NonNullable<typeof item> => item != null);
// js: [0, 1, 'hello', false]
// ts: (number | string | boolean)[]
NonNullable
型は指定した型から null
と undefined
を除去するものです。
これで、ランタイムと型情報いずれからも nullable を除去するフィルタが作成できました。
ユーティリティ化する
上記で紹介したフィルタ関数はユーティリティ化することができます。
const nonNullable = <T>(value: T): value is NonNullable<T> => value != null;
使い所としては例えば、 Array.prototype.map
が undefined
を返しうるとき、最終的に undefined
を除いた配列を返したいケースです。
const users = [
{ id: 0, name: 'Hanako', active: true },
{ id: 1, name: 'Taro', active: false },
{ id: 2, name: 'Natsumi', active: true },
];
// active: true であるユーザーの名前の配列
const activeUserNames = users
.map((user): string | undefined => {
if (!user.active) {
return;
}
return user.name;
})
.filter(nonNullable);
// js: ['Hanako', 'Natsumi']
// ts: string[]
ユーザー定義の Type Guard を使う際の注意
ユーザー定義の Type Guard は実際の処理で返される値と型定義がずれることがあります。
たとえば、次のコードはフィルタ内で Falsy な要素を除去していますが、型定義上は nullable を除去していることになっています。
このコードはエラーが発生しません。
const arr = [0, 1, 'hello', false, null, undefined] as const;
// ts: (number | string | boolean | null | undefined)[]
arr.filter((item): item is NonNullable<typeof item> => !!item);
// js: [1, 'hello']
// ts: (number | string | boolean)[]
JavaScript ランタイムでは false
や 0
も除去されていますが、型定義上では boolean
も返ることになってしまっています。
このような実装と型の乖離を起こさないよう、ユーザー定義の Type Guard を使用する際は注意して実装しましょう。
Discussion