TypeScriptを導入する最大のメリットは、プログラムの実行前にエラーを検知できる「型安全性」にあります。

その中でも、データの集合を扱う配列は、アプリケーション開発において極めて頻繁に使用されるデータ構造です。

JavaScriptでは配列にどのような型の値も混在させることが可能ですが、大規模な開発ではこれが予期せぬバグの原因となります。

TypeScriptで適切に型を指定することで、開発効率を飛躍的に向上させ、メンテナンス性の高いコードを記述できるようになります。

本記事では、モダンな開発環境で必須となる配列の型指定手法を、基礎から応用まで徹底的に網羅します。

配列の型指定における2つの基本形式

TypeScriptにおいて配列の型を定義する際には、大きく分けて2つの記法が存在します。

開発現場で最も一般的に目にするのは、型の後ろに角括弧を付ける「Type[]」形式です。

一方で、ジェネリクスを利用した「Array<Type>」形式という記述方法も存在します。

Type[] 形式 (ショートハンド記法)

この記法は非常にシンプルで、直感的に配列であることを理解しやすいのが特徴です。

基本的にはこちらの記法が推奨されることが多く、多くのプロジェクトのコーディング規約でも標準として採用されています。

TypeScript
// 数値の配列
const numbers: number[] = [1, 2, 3, 4, 5];

// 文字列の配列
const strings: string[] = ["TypeScript", "JavaScript", "Python"];

// ブール値の配列
const flags: boolean[] = [true, false, true];

// 数値配列への追加操作
numbers.push(6);
console.log(numbers);
実行結果
[1, 2, 3, 4, 5, 6]

Array<Type> 形式 (ジェネリクス記法)

ジェネリクスを用いたこの記法は、他の言語から移行してきたエンジニアにとって馴染み深い構造をしています。

機能的には Type[] と全く同じですが、ReactなどのJSXを使用する環境では、不等号 <> がタグと混同される可能性があるため、使い分けに注意が必要です。

TypeScript
// ジェネリクス形式での数値配列
const scores: Array<number> = [80, 90, 75];

// ジェネリクス形式での文字列配列
const names: Array<string> = ["Alice", "Bob", "Charlie"];

console.log(scores[0]);
実行結果
80

2つの記法の比較

どちらの記法を採用すべきか迷う場合は、以下の比較表を参考にしてください。

項目Type[]Array<Type>
可読性簡潔で分かりやすいやや冗長だが明示的
推奨度一般的によく使われるプロジェクトの規約による
多次元配列number[][] となり簡潔Array<Array<number>> となり複雑

読み取り専用の配列を定義する方法

関数に配列を渡す際、その関数内で配列の中身が書き換えられてほしくない場合があります。

そのような「不変性 (Immutability)」を保ちたい場合には、readonly 修飾子を使用します。

readonly Type[] の使用例

readonly を付与すると、pushpop といった破壊的なメソッドが呼び出せなくなります。

TypeScript
const readOnlyNumbers: readonly number[] = [10, 20, 30];

// エラー:読み取り専用プロパティのため、'push' は存在しません
// readOnlyNumbers.push(40);

// エラー:インデックスを介した書き換えも不可
// readOnlyNumbers[0] = 100;

console.log(readOnlyNumbers[0]);
実行結果
10

ReadonlyArray<Type> 形式

ジェネリクス形式で読み取り専用配列を定義する場合は、ReadonlyArray という専用の型を使用します。

TypeScript
const colors: ReadonlyArray<string> = ["red", "green", "blue"];

// colors.push("yellow"); // コンパイルエラー

タプル型 (Tuple) による固定長配列の定義

通常の配列は要素数が可変ですが、「要素の数」と「各要素の型」を固定したい場合はタプル型を使用します。

これは、APIのレスポンスや座標データなど、構造が決まっているデータを扱う際に非常に便利です。

タプル型の基本

角括弧内にそれぞれの要素の型をカンマ区切りで記述します。

TypeScript
// [名前, 年齢, 有効フラグ] のタプル
const user: [string, number, boolean] = ["Tanaka", 25, true];

console.log(user[0]); // "Tanaka" (string)
console.log(user[1]); // 25 (number)

タプル型での注意点

タプル型として定義していても、push メソッドなどで要素を追加できてしまうというTypeScriptの挙動上の制約があります (ただしアクセスはできません)。

より厳密に管理したい場合は、タプル型に対しても readonly を付与することを推奨します。

TypeScript
const point: readonly [number, number] = [100, 200];
// point.push(300); // エラーとなり、安全性が高まる

複数の型を許容する配列 (Union Types)

一つの配列の中に、数値と文字列を混在させたい場合などには Union型 (共用体型) を活用します。

この際、括弧の付け方に注意が必要です。

Union型配列の書き方

(string | number)[] のように記述することで、「文字列または数値の要素を持つ配列」を定義できます。

TypeScript
// 文字列と数値が混在できる配列
const mixedArray: (string | number)[] = ["Apple", 100, "Orange", 200];

// 括弧を忘れると意味が変わるので注意
// string | number[] と書くと、「文字列そのもの」か「数値の配列」という意味になる

オブジェクト配列の型指定

実際の開発で最も多く扱うのが、オブジェクトを要素に持つ配列です。

インターフェース (interface) や型エイリアス (type) と組み合わせて定義するのが一般的です。

interface を使った定義

まずオブジェクトの構造を interface で定義し、その後に配列型として指定します。

TypeScript
interface Product {
    id: number;
    name: string;
    price: number;
}

const products: Product[] = [
    { id: 1, name: "Laptop", price: 120000 },
    { id: 2, name: "Mouse", price: 5000 },
    { id: 3, name: "Keyboard", price: 15000 }
];

products.forEach(p => console.log(p.name));
実行結果
Laptop
Mouse
Keyboard

多次元配列 (入れ子構造) の定義方法

行列データやグリッド構造など、配列の中に配列が含まれる構造を定義する方法を解説します。

基本となる Type[] の後ろに、さらに [] を追加することで階層を表現します。

2次元配列の例

TypeScript
// 数値の2次元配列
const matrix: number[][] = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

const firstRow = matrix[0]; // [1, 2, 3]
const element = matrix[0][1]; // 2

as const (定数アサーション) による厳密な型定義

配列の要素を特定の値のリテラルとして固定したい場合、as const を使用するのが非常に強力なテクニックです。

これにより、配列全体が読み取り専用になり、各要素の型も具体的な値として扱われます。

as const の実例

TypeScript
const categories = ["Food", "Drink", "Others"] as const;

// categories[0] の型は単なる string ではなく "Food" 型になる
// categories.push("NewCategory"); // エラー:読み取り専用のため不可

type Category = typeof categories[number];
// Category 型は "Food" | "Drink" | "Others" という Union 型になる

この手法は、セレクトボックスの選択肢などを定義し、その値から型を自動生成したい場合に頻繁に使用されます。

配列の型推論と最適な指定タイミング

TypeScriptは非常に賢い型推論機能を持っているため、初期値がある場合は明示的な型指定を省略することも可能です。

しかし、空の配列を初期化する場合や、将来的に異なる型の要素が入る可能性がある場合は、明示的な指定が欠かせません。

型推論に任せるケース

TypeScript
// 型指定しなくても number[] と推論される
const autoNumbers = [1, 2, 3];

明示的な指定が必要なケース

TypeScript
// 空配列で初期化する場合、指定しないと any[] になり安全性が失われる
const todoList: string[] = [];

todoList.push("Buy milk");

まとめ

TypeScriptにおける配列の型指定は、単にデータの種類を制限するだけでなく、コードの意図を明確にする強力なツールです。

基本的には Type[] 形式を使用し、不変性を守りたい場面では readonly を活用しましょう。

また、固定長のデータを扱うタプル型や、より厳密な定義が可能な as const を使いこなすことで、さらに堅牢なプログラムを作成できます。

本記事で紹介した様々な手法を、プロジェクトの要件に合わせて適切に使い分けてみてください。

正しい型指定を行うことは、未来の自分やチームメンバーのバグ修正時間を減らす、最も価値のある投資の一つです。