Array.includesが配列の要素と異なる型の値を受け取るとコンパイルエラーが起こる
今回困ったのは以下のような状況で起きたコンパイルエラーです。
// Union型を使った列挙
const SEARCH_PARAM = {
p1: "パラメータ1",
p2: "パラメータ2",
p3: "パラメータ3",
} as const;
type TSearchParam = typeof SEARCH_PARAM[keyof typeof SEARCH_PARAM];
// TSearchParam[]型のincludesメソッドに文字列を渡す
const arr: TSearchParam[] = [SEARCH_PARAM.p1, SEARCH_PARAM.p2];
let value: string = "hello";
arr.includes(value);
// すると、以下のコンパイルエラーが起こる
// Argument of type 'string' is not assignable to parameter of type 'TSearchParam'.(2345)
「TSearchParamを引数として受け取るのでstring型は受け付けないよ」というエラーですね。
ちなみに、SEARCH_PARAM
とTSearchParam
はEnum型の代わりにUnion型で列挙型を表現する方法1です。
JSではincludesメソッドに何を渡してもbooleanが返って来るので楽でしたが、TSの場合引数に型チェックが入ってしまいます。
Array<TSearchParam>.includesなら文字列を受け取れるだろうと想定していたのでちょっと悲しかったです。
エラーの原因
Array.includesの定義を確認すると、以下2のようになっていました。
interface Array<T> {
includes(searchElement: T, fromIndex?: number): boolean;
}
つまり、includesメソッドの引数は配列の要素と同じ型、もしくはそのサブタイプのみしか受け付けないようです。
今回の対処法
配列をany[]型にキャストして、includesメソッドがどんな引数でも受け取れるようにしました。
これでどんな値を受け取ってもコンパイルエラーが起きず、配列に同じ値が含まれるときのみtrueが返り、それ以外の場合にfalseが返ります。
できるだけキャストしたくなかったですが他に思いつかなかったのでこれで対応。
// TSearchParam[]型のincludesメソッドに文字列を渡す
const arr: TSearchParam[] = [SEARCH_PARAM.p1, SEARCH_PARAM.p2];
let value: string = "hello";
(arr as any).includes(value);
おまけ
私と同じようにArray.includesはどんな型でも受け取れるようにしてほしいと思う人がいたようで、GitHubでこんな定義だと嬉しいよねという投稿3がありました。
ある配列のincludesの引数に渡した値の型が、要素の型orそのサブタイプであればbooleanを返し、それ以外の型の場合はfalseを返すという定義です。
interface Array<T> {
includes<U>(searchElement: U, fromIndex?: number): U extends T ? boolean : false;
}