Menu

TypeScriptの配列: 型の書き方、オブジェクトの配列、map・filter

TypeScriptで配列に型を付ける方法。T[] と Array<T> の違い、オブジェクトの配列、複数の型を持つ配列、readonly 配列、map・filter・reduce・find の戻り値の型、リテラルの配列での includes の落とし穴を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

TypeScriptの配列の型は、要素の型のあとに [] を付けて書きます。string[] は文字列の配列、number[] は数値の配列です。ジェネリックの書き方 Array<string> も同じ型です。配列に型を付けると、追加する要素も読み出す要素もすべてその型になります。

@ts-expect-error の行はコンパイルエラー(TS2345)です。ここでは想定済みとしているのでブロックは実行され、型は消去されるので、実行時には 42 が本当に追加されます。出力にもそれが表れています。

string[] vs Array<string>

書き方同じ型補足
string[]Array<string>一般的な書き方。
(string | number)[]Array<string | number>かっこが必要です: string | number[] は「文字列、または数値の配列」という意味になります。
readonly string[]ReadonlyArray<string>push、pop、sort、インデックスへの代入ができません。
User[]Array<User>オブジェクトの配列はオブジェクトの型を使います。
string[][]Array<Array<string>>2次元配列(グリッド)。

コードベースではどちらかひとつのスタイルに統一しましょう。typescript-eslint の array-type ルールのデフォルトは T[] です。

オブジェクトの配列

要素を型エイリアスかインターフェースで表し、Type[] を使います。配列から読み出すものはすべてその形でチェックされます。

users に追加するオブジェクトリテラルは User に正確に一致しなければなりません。admin がなかったり、プロパティ名のつづりを間違えたりするとコンパイルエラーになります。

型付きの map、filter、reduce、find

配列のメソッドはジェネリックなので、結果にも型が付きます。各メソッドが実行時に何をするかはJavaScriptの配列メソッドのページで説明しています。TypeScriptが加えるのは型です:

メソッドT[] での結果の型
map(fn)U[](U は fn の戻り値の型)
filter(fn)T[](またはより狭い型、下を参照)
find(fn)T | undefined
findIndex(fn), indexOf(x)number(ない場合は -1)
some(fn), every(fn), includes(x)boolean
reduce(fn, init)init の型(または型引数 reduce<R>(...))
at(i)T | undefined
join(sep)string

最後の例がうまくいくのは、TypeScript(5.5 以降)が (n) => n !== undefined を型述語として推論し、filter が (number | undefined)[] ではなく number[] を返すからです。推論できないチェックでは、filter((x): x is User => x !== null) のように型述語を自分で書きます。

複数の型を持つ配列

要素の型をユニオンにすると型を混在させられます。配列の型のユニオンでは混在できません:

[name, age] のペアのように位置ごとに型が決まっている場合はタプルを使います。[string, number] はインデックス0が文字列でインデックス1が数値だとわかっていますが、(string | number)[] はそれを知りません。

readonly 配列

readonly T[] は配列を変更するメソッドをすべて取り除きます。関数が変更すべきでないパラメーターや定数に使います。

index.ts(3,12): error TS2339: Property 'push' does not exist on type 'readonly number[]'.

push の行を消せばブロックは 4 を出力します。readonly number[] が期待される場所には、変更可能な number[] をいつでも渡せるので、readonly のパラメーターは呼び出し側に何の負担もかけません。このチェックはコンパイル時だけで、実行時には普通の配列です。readonly 配列を並べ替えるには、コピーを並べ替えます: [...values].sort()。

リテラルの配列での includes の落とし穴

as const は配列をリテラル型の readonly タプルにします。許可された値の一覧には便利ですが、その includes はそのリテラルしか受け付けなくなります:

チェックを型ガード(value is Color)で包めば、配列を広げるのは1か所で済み、呼び出し側には絞り込まれた値が返ります。

インデックスアクセスと空の配列

arr[i] を読むと、i が範囲外で実行時の値が undefined でも型は T です。at(i) の型は T | undefined で、コンパイラーオプション noUncheckedIndexedAccess を有効にすると普通のインデックスアクセスも T | undefined を返すようになります。

queue[0].toUpperCase() はコンパイルが通り、実行時に TypeError を投げます。インデックスが存在しないかもしれない場合は、at()、長さのチェック、noUncheckedIndexedAccess のいずれかを使いましょう。

よくある質問

TypeScriptで配列の型を宣言するには?

要素の型のあとに [] を書きます: let names: string[] = ["a", "b"]。ジェネリックの書き方 Array<string> もまったく同じ意味です。オブジェクトの配列では、オブジェクト型やインターフェースを要素の型にします: User[]。

string[] と Array<string> の違いは何ですか?

違いはありません。同じ型の2つの書き方です。よく使われるのは string[] です。要素の型が複雑な場合はジェネリックの書き方のほうが読みやすく、readonly の配列でも readonly string[] と ReadonlyArray<string> は同じです。

TypeScriptで find が undefined を返すのはなぜですか?

一致する要素がないかもしれないので、array.find() は T | undefined を返します。strict のもとでは、結果を使う前に if のチェック、オプショナルチェーン(found?.name)、デフォルト値(found ?? fallback)のいずれかで undefined の場合を処理する必要があります。

TypeScriptで複数の型を持つ配列の型は?

要素の型をかっこで囲んだユニオンにします: (string | number)[] は、各要素が文字列か数値である配列です。これは string[] | number[] とは違います。こちらは文字列だけの配列か、数値だけの配列のどちらかです。[string, number] のように型の順序が決まっている場合はタプルを使います。

as const の配列で includes がエラーになるのはなぜですか?

["red", "green"] as const のようなリテラルの readonly 配列は includes(searchElement: "red" | "green") を持つので、普通の string を渡すとエラー TS2345 になります。チェックのときだけ配列を広げます: (COLORS as readonly string[]).includes(input)。input をリテラルのユニオンに絞り込む型ガードの中で行うのが理想です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める