🐦

引数で受け取ったUnion型にユーザー定義型ガードするutil関数

2023/06/04に公開2

はじめに

「ある変数をUnion型に型ガードしたい」という場面によく出くわすのですが、自分で定義した型に型ガードしたい場合はtypeofinstanceofなどでは絞り込みないため、ユーザー定義型ガードのための関数を定義する必要があります。

https://typescript-jp.gitbook.io/deep-dive/type-system/typeguard#yznotype-guard

必要な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ファイルで使用する場合は型引数の末尾にカンマ,を打つ必要があります。

おわりに

本当は型引数としてUnion型を渡せるようにできれば"それっぽく"なると思ったのですが、できませんでした。
Union型に絞り込むためにはその値を持った配列がどうしても必要なのですが、「型→値をもつ配列」という変換(宣言)ができなかったためです。

イメージ
if (isIncluded<'hoge' | 'fuga'>(arg)) {
  arg; // "hoge" | "fuga"
}
GitHubで編集を提案

Discussion

ootideaootidea

TypeScript 5.0以上なら<const T>を使って定義することでas constが不要になったりしますね

const isIncluded = <const T>(arr: readonly T[], arg: any): arg is T => arr.includes(arg);
MenToRiMenToRi

ありがとうございます!記事に反映させました。