5.

TypeScriptの配列(Array)型|書き方・readonly・タプルとの違い

編集

TypeScriptの配列(Array)型とは、同じ型の値を順序付きで並べて保持するデータ構造に対して、その要素の型を静的にチェックするための型です。書き方は number[]Array<number> の2通りがあり、どちらも「number型の値だけが入る配列」を表します。実行時のデータ構造はJavaScriptの配列そのものですが、コンパイル時に要素の型・操作の戻り値・代入の可否がチェックされる点が大きな違いです。

この記事の要点
  • 配列型の書き方は number[](簡潔記法)と Array<number>(ジェネリック記法)の2通りで、意味は同じ。
  • 初期化時の値から型が推論されるため、多くの場合は明示しなくてよい。空配列だけは推論できず注意。
  • 変更を防ぎたいときは readonly number[] または ReadonlyArray<number> を使う。
  • タプル[string, number])は「長さと各位置の型が固定」される点で通常の配列と異なる。
  • 複数の型が混在する配列は (string | number)[] のようにユニオンで表す。
  • any[] は型チェックを無効化するため避け、不明なら unknown[] を検討する。

配列型の2つの書き方

TypeScriptでは配列型を次の2つの記法で書けます。どちらも完全に同じ型を表し、相互に代入できます。

// 簡潔記法(角括弧)

let list: number[] = [1, 2, 3];

 

// ジェネリック記法

let list2: Array<number> = [1, 2, 3];

どちらを使うかは好みやチームの規約によります。一般には簡潔な number[] が広く使われますが、要素の型自体が複雑な場合は Array<...> のほうが読みやすいことがあります。文字列の配列は string[]、真偽値の配列は boolean[] のように書きます。

型推論と明示

変数の初期化と同時に配列リテラルを代入すると、TypeScriptは値から要素の型を推論します。そのため、明示的に型注釈を書かなくても型が決まります。

// 型注釈なしでも number[] と推論される

const nums = [1, 2, 3];   // number[]

const names = ["a", "b"];  // string[]

 

// 空配列は any[] になりやすいので型を明示する

const empty: number[] = [];

注意したいのは空配列です。const empty = []; のように初期値が空だと要素の型を推論できず、any[](厳密な設定下では暗黙のanyとして扱われる)になりがちです。後から型安全に使いたい場合は const empty: number[] = []; のように型注釈を付けましょう。

多次元配列

配列の要素自体を配列にすると多次元配列になります。角括弧を重ねて表記します。

// 2次元配列(number[] の配列)

const grid: number[][] = [

  [1, 2, 3],

  [4, 5, 6],

];

 

const value = grid[0][1];  // number

number[][] は「number[] を要素に持つ配列」という意味で、Array<Array<number>> と書いても同じです。

読み取り専用配列(readonly)

配列の中身を後から変更させたくない場合は、読み取り専用配列を使います。書き方は readonly number[] または ReadonlyArray<number> の2通りで、意味は同じです。

const ro: readonly number[] = [1, 2, 3];

 

ro.push(4);   // エラー: push は存在しない

ro[0] = 99;   // エラー: 読み取り専用のため代入不可

const len = ro.length;  // OK(読み取りは可能)

読み取り専用配列では pushpopsplice のような中身を変更するメソッドや、添字での代入が型エラーになります。一方で mapfilterlength など中身を変えない操作は使えます。なお、これはあくまでコンパイル時のチェックであり、実行時に凍結されるわけではない点に注意してください(実行時に変更を禁止したい場合は Object.freeze を併用します)。

タプル(Tuple)との違い

通常の配列は「同じ型の要素が任意個」並ぶのに対し、タプルは「各位置の型と長さが固定された」配列型です。[string, number] のように位置ごとに型を指定します。

// 配列: number がいくつでも入る

const arr: number[] = [1, 2, 3];

 

// タプル: 1番目は string、2番目は number で固定

const pair: [string, number] = ["age", 30];

 

const bad: [string, number] = [30, "age"]; // エラー: 型の順序が不一致

観点 配列 number[] タプル [string, number]
要素の型すべて同じ型位置ごとに異なる型を指定可
長さ可変(任意個)原則固定(要素数が型の一部)
用途同種データの集まり意味の異なる値の組み合わせ
添字アクセスの型常に要素型(例: number)位置に応じた型

ユニオン要素の配列

1つの配列に複数の型を混在させたいときは、要素の型をユニオン(|)で表し、全体を括弧でくくって配列にします。括弧の位置で意味が変わるので注意が必要です。

// 各要素が string または number の配列

const mixed: (string | number)[] = [1, "two", 3];

 

// 注意: これは「string 単体」または「number[]」という別物

let other: string | number[];

(string | number)[] は「各要素がstringかnumber」を意味します。括弧を外した string | number[] は「string そのもの、または number の配列」という全く別の型になるため、混同しないようにします。

主な操作メソッドと型

配列の各メソッドは型情報を引き継ぐため、戻り値やコールバック引数にも型が付きます。代表的なものを示します。

const nums: number[] = [1, 2, 3];

 

nums.push(4);              // 戻り値: number(新しい長さ)

const doubled = nums.map(n => n * 2);   // number[](n は number)

const evens = nums.filter(n => n % 2 === 0); // number[]

const found = nums.find(n => n > 2);       // number | undefined

const sum = nums.reduce((a, b) => a + b, 0); // number

ポイントは find の戻り値が number | undefined になることです。条件に合う要素が見つからない可能性があるため、結果を使う前に存在チェック(または?.などのオプショナル処理)が必要になります。map は変換後の型に合わせて新しい配列型を返し、filter は元の要素型の配列を返します。

any[]を避ける

要素の型を any[] にすると、その配列に対する型チェックがほぼ無効になり、TypeScriptを使う利点が失われます。型が事前に分からない場合でも、できるだけ any[] の使用は避けます。

// 避けたい: 何でも入り、誤用も検出されない

const loose: any[] = [1, "x", true];

loose[0].foo.bar; // エラーにならない(実行時に壊れる)

 

// 代替: 使う前に型を絞る必要がある

const safe: unknown[] = [1, "x", true];

unknown[] なら「中身は不明だが配列ではある」という安全な出発点になり、要素を使う前に型の絞り込みを強制できます。型が決まっているなら (string | number)[] のように具体的に書くのが最善です。

落とし穴

注意点
  • JSの配列との型の違い: 実行時のデータはJavaScriptの配列のままで、型はコンパイル時のみのチェック。型を信じきって実行時の入力(APIレスポンスなど)を検証しないと、型と実体がずれることがある。
  • 範囲外アクセス: arr[100] のように存在しない添字でも、既定では型上は要素型(例: number)と推論され、実際には undefined が返る。noUncheckedIndexedAccess を有効にすると添字アクセスが number | undefined となり、より安全になる。
  • 共変性(variance)の罠: TypeScriptの配列は基本的に共変で、Dog[]Animal[] として扱える。便利だが、その参照経由で別種の要素を push すると実体と型がずれる危険がある。変更させたくない箇所は readonly 配列を使うと安全。
  • 空配列の推論: [] 単独は型が定まらず暗黙のanyになりがち。型注釈を付ける。

FAQ

Q. number[]Array<number> はどちらを使うべき?
A. 機能差はなく完全に同義です。多くのスタイルガイドは簡潔な number[] を推奨しますが、要素型が長く複雑な場合は Array<...> が読みやすいこともあります。チームの規約に合わせれば問題ありません。

Q. readonly number[] にすれば実行時にも変更できなくなる?
A. いいえ。readonly はコンパイル時の型チェックだけで、実行時の値そのものは普通の配列のままです。実行時に変更を禁止したい場合は Object.freeze を併用してください。

Q. タプルと配列はどう使い分ける?
A. 同じ意味の値を任意個まとめるなら配列(number[])、意味の異なる値を決まった順序・個数で組にするならタプル([string, number])を使います。関数から複数の値をまとめて返すときなどはタプルが向きます。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. String(文字列)
  2. Number (数値: 整数/浮動小数点数)
  3. Boolean (真偽値: true/false)
  4. Any (全ての型を許容)
  5. Array(配列)
  6. Tuple (値のペア)

最近更新/作成されたページ