以前 Union 型に含まれるか判定するのに 配列を array.some
で回して調べる方法を描いていました
array.includes()
だと Type error になるという情報を見かけたので調べてみたのメモ
環境
TypeScript v4.9.5
(TypeScript Playground)
array.includes()
だと Type error になる!
const arr = ['Ichigo', 'Aoi', 'Ran'] as const; // const arr: readonly ["Ichigo", "Aoi", "Ran"] type SoleilType = typeof arr[number]; // type SoleilType = "Ichigo" | "Aoi" | "Ran" const isSoleil = (val: string): val is SoleilType => { return arr.includes(val); // Argument of type 'string' is not assignable to parameter of type '"Ichigo" | "Aoi" | "Ran"'. }; const str = 'Yurika'; isSoleil(str);
arr.include(val)
の箇所で Argument of type 'string' is not assignable to parameter of type '"Ichigo" | "Aoi" | "Ran"'.
という型エラーが発生しました
配列の中身が定数なら同様にエラーになる
const ICHIGO = 'Ichigo' as const; const AOI = 'Aoi' as const; const RAN = 'Ran' as const; const arr = [ICHIGO, AOI, RAN]; // const arr: ("Ichigo" | "Aoi" | "Ran")[] type SoleilType = typeof arr[number]; // type SoleilType = "Ichigo" | "Aoi" | "Ran" const isSoleil = (val: string): val is SoleilType => { return arr.includes(val); // Argument of type 'string' is not assignable to parameter of type '"Ichigo" | "Aoi" | "Ran"'. };
解消方法 1. array.some()
を使う
array.inclides
を使わなくてもいいなら array.some()
に置き換えてしまえば良い
type SoleilType = typeof arr[number]; // type SoleilType = "Ichigo" | "Aoi" | "Ran" const isSoleil = (val: string): val is SoleilType => { - return arr.includes(val); + return arr.some((v) => v === val); }; const str = 'Yurika'; isSoleil(str); // => false
解消方法 2. array.includes()
に渡す引数を any 型にキャストしてしまう
array.prototype.include
は次のように定義されている
declare global { interface Array<T> { includes<U extends (T extends U ? unknown : never)>( searchElement: U, fromIndex?: number): boolean; } }
Yes, technically it should be safe to allow the
searchElement
parameter inArray<T>.includes()
to be a supertype ofT
, but the standard TypeScript library declaration assumes that it is justT
. For most purposes, this is a good assumption, since you don't usually want to compare completely unrelated types as @GustavoLopes mentions. But your type isn't completely unrelated, is it?
cf. javascript - Why does the argument for Array.prototype.includes(searchElement) need the same type as array elements? - Stack Overflow
string[]
のような配列に number
の値が含まれるかなんて調べないよね?って想定で array.includes()
の引数は配列に含まれる型 (Array<T>
の T
) であることとされているらしい
なので as const
された配列や含まれる要素が定数の場合は、配列に含まれる型 = 定数 になるので string 型などを渡そうとするとエラーになってしまうみたい
なのだけど、array.includes()
に渡す引数を as any
で any 型にしてしまうと配列に含まれる型として担保されないのだけどコンパイラが通る
type SoleilType = typeof arr[number]; // type SoleilType = "Ichigo" | "Aoi" | "Ran" const isSoleil = (val: string): val is SoleilType => { - return arr.includes(val); + return arr.includes(val as any); }; const str = 'Yurika'; isSoleil(str); // => false
any 型は全ての型の親みたいなものなので、下記のような流れでコンパイラが通るのではないかと思う
includes<U extends (T extends U ? unknown : never)> ↓ includes<any extends (T extends any ? unknown : never)> ↓ includes<any extends unknown>
個人的な憶測だが any の場合は配列に含まれる型である可能性もあるので 100% 異なると言い切れないので受け付ける様になっているのではと思いました
まとめ
配列に含まれている値かチェックしたい時は
array.some()
を使うarray.includes()
を使う場合、定数の配列なら引数をas any
にキャストして渡す
とおぼえておけば良さそうです
個人的には array.some()
で良いんじゃないかな〜という印象
おわり ₍ ᐢ. ̫ .ᐢ ₎
[参考]
- typescript - Argument of type 'string' is not assignable to parameter of type union of string consts - Stack Overflow
- TypeScript Union 型に含まれる値かどうか判定したい - かもメモ
- Array.prototype.some() - JavaScript | MDN
- Array.prototype.includes() - JavaScript | MDN
- javascript - Why does the argument for Array.prototype.includes(searchElement) need the same type as array elements? - Stack Overflow
- `Array.includes` type is too narrow · Issue #26255 · microsoft/TypeScript · GitHub