🧩

TypeScriptのArray.includesは使うな

2023/07/07に公開
7

なぜ使ってはいけないのか

早速ですがこのコードを見てください。

const arr = ['Hello'] as const
const str = 'Includes'

if (arr.includes(str)) {
  console.log(`Hello ${str}`)
} else {
  console.log(str)
}

実行結果は以下です。

Includes

Includesですね。
きちんと判定できています。

...そう、esbuild(型チェックなし)ならね。

やっぱり型使いたい

では型情報を見てみましょう。

エラー出ていますね。これではコンパイルできません。

コンパイルできるようにするには

ではどうするかというと...

const arr = ['Hello'] as const
const str = 'Includes'

-if (arr.includes(str)) {
+if ((arr as ReadonlyArray<string>).includes(str)) {
  console.log(str) // str: Includes
} else {
  console.log(str) // str: Includes
}

この部分を追加しました。

(arr as ReadonlyArray<string>)

...せっかく厳密な型にしているのに無駄になっちゃいましたね。
そしてif文内のstrIncludesになっています。到達しないのに。

ちなみに

const str = 'Includes' as string

とかするとincludesで振り分けられたあともstringになってしまいます。
これでは開発速度に影響出そうです。

Array.includesを使うな

(やっとタイトル回収)

コメントで教えていただきました
ts-resetを使うと良さそうです
組み込みの型情報を書き換えてくれる優れものです

追記: 業務で使っていますがかなり良いです。


※ここから下はts-reset関係ないです

以下のような関数を定義すると解決します。

function includes<A extends ReadonlyArray<unknown>>(array: A, input: unknown): input is A[number] {
  return array.includes(input)
}

使い方はこうです。

const arr = ['Hello'] as const
const str = 'Includes'

if (includes(arr, str)) {
  console.log(`Hello ${str}`)
} else {
  console.log(str)
}

型はこうなります。

const arr = ['Hello'] as const
const str = 'Includes'

if (includes(arr, str)) {
  console.log(`Hello ${str}`) // str: never
} else {
  console.log(str) // str: 'Includes'
}

arr = ['Includes']にすると...

const arr = ['Includes'] as const
const str = 'Includes'

if (includes(arr, str)) {
  console.log(`Hello ${str}`) // str: 'Includes'
} else {
  console.log(str) // str: never
}

str = 'Includes' as stringにすると...

const arr = ['Includes'] as const
const str = 'Includes' as string

if (includes(arr, str)) {
  console.log(`Hello ${str}`) // str: 'Includes'
} else {
  console.log(str) // str: string
}

ちゃんと型付きましたね! Hello Includes!

おわり

キャスト警察👮‍♀が来るので
型が付くと開発速度向上やバグ撲滅に繋がるのでasはやめよう!

Discussion

Hiraku OhnoHiraku Ohno

String.includesを使うな、ではなく Array<string>.includes を使うな、でしょうか?

こめこめ

仰るとおりです(疲れてたんだと思います... 修正しました)
細かいですがArray<T>.includesみたいな感じになってたと思います

明日村音太郎明日村音太郎
const arr = ["Hello"] as readonly string[]; 
const str = 'Includes'

if (arr.includes(str)) {
  console.log(`Hello ${str}`)
} else {
  console.log(str)
}

こんなのはどうでしょうか!?

明日村音太郎明日村音太郎

すみません。趣旨を理解しないまま、盛り上がってしまいました。お恥ずかしい。
arrを厳密な型のまま、includesで判定したかったわけなんですね。お役にも立てそうにないです…
ts-resetだけありがたく試させてもらいます。