🐦
引数で受け取ったUnion型にユーザー定義型ガードするutil関数
はじめに
「ある変数をUnion型に型ガードしたい」という場面によく出くわすのですが、自分で定義した型に型ガードしたい場合はtypeof
やinstanceof
などでは絞り込みないため、ユーザー定義型ガードのための関数を定義する必要があります。
必要なUnion型毎に都度関数を定義するのが面倒だと感じたため、引数で受け取ったUnion型に型ガードしてくれるutil関数を考えました。
結論
/**
* @summary 変数を受け取った配列のUnion型に型ガードする
* @param {readonly any[]} arr 絞り込みたいUnion型の値をもつ配列(`readonly`)
* @param {any} arg 絞り込みたい対象の変数
*/
const isIncluded = <T>(arr: readonly T[], arg: any): arg is T => arr.includes(arg);
- 引数1. 絞り込みたいUnion型の値を持つ配列(
readonly
) - 引数2. 型ガードしたい対象の変数
例1.
let a: string;
if (isIncluded(['1', '2', '3'] as const, a)) {
a; // "1" | "2" | "3"
}
例2.
const array = ['hoge', 'fuga', true, 0, null];
for (const value of array) {
if (isIncluded(['fuga', null] as const , value)) {
value; // "fuga" | null
}
}
例3.
const obj = {
hoge: 'aaa',
fuga: 'bbb'
} as const;
const arr: string[] = [];
for (const value of arr) {
obj[value]; // error: expression of type 'string' can't be used to index
if (isIncluded(Object.keys(obj) as (keyof typeof obj)[], value)) {
const prop = obj[value];
prop; // "aaa" | "bbb"
}
}
実装に関して
- 第1引数で受け取った
T[]
の配列に第2引数の変数が含まれるかどうかをArray#includes()
で判定し、true
ならばarg is T
としてユーザー定義型ガードします。 - 配列は
readonly
でないとUnion型に絞り込めないため、as const
して渡します。- [追記] const型パラメータを用いればその限りではありません。
- 型引数
<T>
は自動で推論されるため、基本的に指定の必要はありません。 -
.tsx
ファイルで使用する場合は型引数の末尾にカンマ,
を打つ必要があります。const isIncluded = <T,>(...
- 参考: https://bobbyhadz.com/blog/typescript-arrow-function-generic#when-working-in-tsx-files-use-a-trailing-comma
おわりに
本当は型引数としてUnion型を渡せるようにできれば"それっぽく"なると思ったのですが、できませんでした。
Union型に絞り込むためにはその値を持った配列がどうしても必要なのですが、「型→値をもつ配列」という変換(宣言)ができなかったためです。
イメージ
if (isIncluded<'hoge' | 'fuga'>(arg)) {
arg; // "hoge" | "fuga"
}
Discussion
TypeScript 5.0以上なら
<const T>
を使って定義することでas const
が不要になったりしますねありがとうございます!記事に反映させました。