TypeScriptの基礎 - 配列とタプル

提供: MochiuWiki : SUSE, EC, PCB

概要

TypeScriptでは、JavaScriptの配列に対して型情報を付与することで、要素の型をコンパイル時に厳密に管理できる。
さらに、タプル型と呼ばれる固定長かつ各位置の型が決定した配列型を定義することで、データ構造の型安全性を高められる。

配列とタプルの型定義
機能 説明
配列の型定義 T[] 構文と Array<T> ジェネリック構文の2つの記法がある。
readonly 修飾子 または ReadonlyArray<T> による読み取り専用配列、
多次元配列の定義も可能
タプル型 固定長で各インデックスの型を個別に指定する配列型
TypeScript 4.0以降でラベル付きタプル、可変長タプル型 (Variadic Tuple Types) が導入された。
オプショナル要素 (?)、読み取り専用タプル (readonly) にも対応
as const アサーション 配列リテラルをリテラル型の読み取り専用タプルとして推論させる機能
ユニオン型の定数セットの簡潔な定義に活用できる。
分割代入との組み合わせ タプルの各要素に名前を付けて取り出し、関数の戻り値への応用が可能


TypeScript 5.0では const 型パラメータが導入され、ジェネリック関数の型パラメータに const を付与することで、配列引数がリテラル型のタプルとして推論されるようになった。
TypeScript 5.4では NoInfer ユーティリティ型が追加され、ジェネリック関数での型推論のコントロールが改善されている。

2026年2月時点でTypeScript 5.9が安定版としてリリースされている。
TypeScript 6.0ベータでは strict: true のデフォルト化等の変更が行われており、Go言語で書き直されるTypeScript 7.0では最大10倍のコンパイル速度向上が見込まれている。


配列の型定義

TypeScriptでは、配列の要素の型を明示することで、誤った型の値が混入することをコンパイル時に防止できる。

配列型の定義方法として、T[] 構文と Array<T> ジェネリック構文の2種類がある。

T[] と Array<T>

T[] 構文は、最も一般的な配列型の定義方法である。
型名の後ろに [] を付与することで、その型の配列を表す。

 let numbers: number[] = [1, 2, 3];
 let strings: string[] = ["a", "b", "c"];
 let booleans: boolean[] = [true, false];


ユニオン型の配列を定義する場合は、ユニオン型をグループ化するために丸括弧で囲む必要がある。

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


Array<T> ジェネリック構文は、型をジェネリクスのパラメータとして指定する方法である。
T[]Array<T> は基本的に同等であり、どちらを使用しても同一の型を表す。

 let numbers: Array<number> = [1, 2, 3];
 let strings: Array<string> = ["a", "b", "c"];


Array<T> 構文は、関数型など複雑な型を要素に持つ配列を定義する際に可読性が高くなる場面がある。

 // 関数型を要素に持つ配列 : Array<T> の方が読みやすい場合がある
 let handlers: Array<(event: Event) => void> = [];


T[]Array<T> の使い分けについては、以下に示すガイドラインが参考になる。

T[] と Array<T> の使い分け
場面 説明
単純な型定義 T[] を使用する。
シンプルで読みやすい。
複雑なジェネリック型 Array<T> を使用すると可読性が向上する場合がある。
プロジェクト内での一貫性 どちらかに統一することが最も重要である。


読み取り専用配列

配列を変更不可にするには、readonly 修飾子 または ReadonlyArray<T> ジェネリック型を使用する。
両者は同等であり、pushpopsplice 等の変更メソッドが使用不可になる。

 // readonly 修飾子
 let readonlyArr: readonly string[] = ["a", "b", "c"];
 
 // ReadonlyArray<T> ジェネリック型
 let readonlyArr2: ReadonlyArray<string> = ["a", "b", "c"];


読み取り専用配列に対して変更を試みると、コンパイル時にエラーが発生する。

 readonlyArr[0] = "x";   // エラー : Index signature in type 'readonly string[]' only permits reading.
 readonlyArr.push("d");  // エラー : Property 'push' does not exist on type 'readonly string[]'.


読み取り専用配列は、関数のパラメータに使用することにより、関数内で配列が変更されないことを保証できる。

また、number[] 型 (mutable) の変数を readonly number[] 型のパラメータに渡すことは可能だが、逆方向の代入はできない。

 function processArray(arr: readonly number[]): number {
    return arr.reduce((sum, n) => sum + n, 0);
 }
 
 let mutable: number[] = [1, 2, 3];
 let immutable: readonly number[] = [4, 5, 6];
 
 processArray(mutable);    // OK : mutableをreadonlyパラメータに渡せる
 processArray(immutable);  // OK
 
 // 逆方向 : readonlyをmutableに代入はできない
 let target: number[] = immutable;  // エラー


多次元配列

多次元配列は、配列型の後ろに [] を追加することで定義する。

 let matrix: number[][] = [[1, 2], [3, 4]];
 let grid: string[][]   = [["a", "b"], ["c", "d"]];
 let cube: number[][][] = [[[1]], [[2]], [[3]]];


読み取り専用の多次元配列を定義する場合は、外側と内側の両方に readonly を付与する必要がある。

 // 外側のみreadonly : 外側の配列の要素置換は不可だが、内側の配列は変更可能
 let readonlyOuter: readonly number[][] = [[1, 2], [3, 4]];
 
 // 外側・内側ともにreadonly : 全ての変更操作が不可
 let fullyReadonly: readonly (readonly number[])[] = [[1, 2], [3, 4]];



タプル型

タプル型は、固定長で各位置の型が個別に決定している配列型である。

通常の配列型 (例: number[]) では全要素が同一型または最良共通型になるのに対し、タプル型では各インデックスの型を個別に指定できる。

基本的な構文

タプル型は、角括弧内に各要素の型をカンマ区切りで列挙して定義する。

 let tuple: [string, number] = ["age", 25];
 let tuple3: [string, number, boolean] = ["name", 30, true];


タプル型は、各インデックスの型と要素数の両方が厳密にチェックされる。

 let tuple: [string, number];
 
 tuple = ["hello", 42];        // OK
 tuple = [42, "hello"];        // エラー : 型の順序が異なる
 tuple = ["hello", 42, true];  // エラー : 要素数が異なる


型注釈なしで配列リテラルを定義すると、TypeScriptはタプル型ではなく配列型を推論する点に注意が必要である。

 let inferred = ["a", 1];                   // 型: (string | number)[] (配列型として推論)
 let asTuple: [string, number] = ["a", 1];  // 型: [string, number] (タプル型)


ラベル付きタプル

TypeScript 4.0以降では、タプルの各要素にラベル (名前) を付与できる。

ラベルを付与することにより、各要素の意味が明確になり、IDEがラベル名を表示してソースコードの可読性が向上する。

 let labeled: [name: string, age: number] = ["Alice", 30];
 let point: [x: number, y: number] = [10, 20];
 let response: [status: number, data: string, ok: boolean] = [200, "success", true];


ラベルはドキュメント的な役割を果たすものであり、実行時には影響しない。
関数の残余パラメータにラベル付きタプルを使用すると、パラメータの意味を明確に示すことができる。

 function processUser(...args: [id: number, name: string]) {
    console.log(`User ${args[1]} with ID ${args[0]}`);
 }


オプショナル要素

タプルの各要素を省略可能にするには、要素の型の後ろに ? を付与する。

オプショナル要素は型が T | undefined のユニオン型として扱われる。

 let optional: [string, number?] = ["hello"];       // OK : 2番目の要素を省略
 let optional2: [string, number?] = ["hello", 42];  // OK : 2番目の要素を指定


複数のオプショナル要素を持つタプルも定義できる。

 type FormData = [
    name: string,
    email?: string,
    phone?: string,
    subscribe?: boolean
 ];
 
 const form1: FormData = ["John"];
 const form2: FormData = ["Jane", "jane@example.com"];
 const form3: FormData = ["Bob", "bob@example.com", "090-0000-0000", true];


オプショナル要素の後ろに必須要素を配置することはできない。
必須要素は、常にオプショナル要素より前に配置する必要がある。

可変長タプル

TypeScript 4.0以降では、可変長タプル型 (Variadic Tuple Types) を使用して、可変長の要素を含むタプルを定義できる。

スプレッド構文 (...型[]) を使用して、任意の数の要素を受け入れる部分を定義する。

 // 先頭がstringで、残りが任意の数のnumber
 type StringNumber = [string, ...number[]];
 
 const a: StringNumber = ["hello"];           // OK : number部分は0個でも可
 const b: StringNumber = ["hello", 1, 2, 3];  // OK : number部分は複数でも可


スプレッド部分の前後に固定型の要素を配置することもできる。

 // 先頭が string、中間が任意の数の number、末尾が boolean
 type WithEnd = [string, ...number[], boolean];
 
 const d: WithEnd = ["start", 1, 2, 3, true];  // OK
 const e: WithEnd = ["start", true];           // OK : 中間のnumber[]は、0個でも可


スプレッド部分を先頭に配置することも可能である。

 // 先頭が任意の数のboolean、末尾がstringとnumber
 type BooleansStringNumber = [...boolean[], string, number];
 
 const valid: BooleansStringNumber  = [true, false, "a", 42];  // OK
 const valid2: BooleansStringNumber = ["a", 42];               // OK : boolean[]は0個でも可


読み取り専用タプル

タプルを変更不可にするには、タプル型定義の前に readonly 修飾子を付与する。

 let readonlyTuple: readonly [string, number]       = ["hello", 42];
 let readonlyPoint: readonly [x: number, y: number] = [10, 20];


読み取り専用タプルに対して変更を試みると、コンパイル時にエラーが発生する。

 readonlyTuple[0] = "world";  // エラー : Cannot assign to '0' because it is a read-only property.
 readonlyTuple.push(true);    // エラー : Property 'push' does not exist on type 'readonly [string, number]'.



分割代入との組み合わせ

配列とタプルは、JavaScript / TypeScript の分割代入 (Destructuring Assignment) と組み合わせることにより、各要素に名前を付けて取り出すことができる。

基本的な分割代入を以下に示す。

 let tuple: [string, number] = ["hello", 42];
 const [str, num] = tuple;
 // strの型: string
 // numの型: number


可変長タプルの分割代入では、残余要素にスプレッド構文を使用できる。

 const [first, ...rest] = ["values", 1, 2, 3] as [string, ...number[]];
 // firstの型: string
 // restの型 : number[]


関数の戻り値にタプル型を使用することにより、複数の値を型安全に返すことができる。

 function getCoordinates(): [number, number] {
    return [10, 20];
 }
 
 const [x, y] = getCoordinates();
 // x の型: number、y の型: number


関数パラメータ内での分割代入も可能である。

 function processPoint([x, y]: [number, number]) {
    console.log(`Point: (${x}, ${y})`);
 }
 
 processPoint([5, 10]);


特定の要素をスキップして取り出すには、カンマで位置を明示する。

 let tuple: [string, number, boolean] = ["a", 1, true];
 const [first, , third] = tuple;  // 2番目の要素をスキップ
 // firstの型: string
 // thirdの型: boolean


as const アサーションを配列リテラルに適用すると、型推論においてその配列をリテラル型の読み取り専用タプルとして扱わせることができる。

 // as constなし
 const arr = [1, 2];             // 型: number[]
 
 // as constあり
 const tuple = [1, 2] as const;  // 型: readonly [1, 2]


文字列リテラルの配列に as const を適用すると、ユニオン型の定数セットを簡潔に定義できる。

 const colors = ["red", "green", "blue"] as const;
 // 型: readonly ["red", "green", "blue"]
 
 type Color = typeof colors[number];  // 型: "red" | "green" | "blue"


関数の戻り値に as const を使用すると、呼び出し側で正確なタプル型が推論される。

 function createPoint() {
    return [10, 20] as const;  // 型: readonly [10, 20]
 }
 
 const [px, py] = createPoint();
 // pxの型: 10 (リテラル型)
 // pyの型: 20 (リテラル型)


as const の主な効果を以下に示す。

  • 全ての要素がリテラル型になる。
  • 配列が自動的に readonly になる。
  • 配列がタプル型として扱われる。(ワイドな配列型にならない)



関連情報