TypeScript | 基礎文法:配列・タプル – 可変長タプル

TypeScript
スポンサーリンク

可変長タプルとは何か(「一部だけ長さが伸びるタプル」)

タプルは本来「要素数が固定」の型ですが、
「ここまでは固定、それ以降は同じ型がいくつでも続いていい」
という形を表現できるのが「可変長タプル(可変長タプル型)」です。

ざっくり言うと、

  • 先頭の何個かは「位置と型が決まっている」
  • そのあとに「同じ型の要素が0個以上続いてもいい」

という“ハイブリッドな配列”を型で表現できます。

type Log = [string, ...number[]];

const a: Log = ["sum"];          // OK(後ろが0個)
const b: Log = ["sum", 1, 2, 3]; // OK(後ろが複数)
TypeScript

ここでは「1番目は必ず string」「2番目以降は number がいくつでも」という形になっています。


基本構文:…[型[]] をタプルの中に埋め込む

「固定部分」+「可変部分」を1つのタプルで表す

可変長タプルの基本構文は、
タプルの中に ...T[] の形で「伸びる部分」を書くことです。

type T = [A, B, ...C[]];
TypeScript

という形で、

  • A, B は位置と型が固定
  • C[] が0個以上続いてもいい

という意味になります。

具体例で見るとイメージしやすいです。

type Log = [string, ...number[]];

const x: Log = ["sum"];          // OK
const y: Log = ["sum", 1];       // OK
const z: Log = ["sum", 1, 2, 3]; // OK
// const ng: Log = [1, 2, 3];    // NG:先頭が string じゃない
TypeScript

「先頭だけは絶対この型」「そのあとに同じ型が続く」という構造を、
1つのタプル型で表現できています。


典型例1:関数の引数リストを表現する

「最初の引数は固定、それ以降は任意個」のパターン

たとえば、「最初の引数はメッセージ、それ以降は数値のパラメータ」という関数を考えます。

function logSum(label: string, ...values: number[]) {
  console.log(label, values.reduce((a, b) => a + b, 0));
}
TypeScript

このとき、引数全体の型は「可変長タプル」で表現できます。

type LogArgs = [string, ...number[]];

function logSum(...args: LogArgs) {
  const [label, ...values] = args;
  console.log(label, values.reduce((a, b) => a + b, 0));
}

logSum("total");        // OK
logSum("total", 1, 2);  // OK
logSum("total", 1, 2, 3); // OK
// logSum(1, 2, 3);     // NG:先頭が string じゃない
TypeScript

ここで重要なのは、

  • 「先頭は必ず string」
  • 「2番目以降は number がいくつでも」

という仕様を、型としてそのまま表現できていることです。
ただの any[](string | number)[] では、この「先頭だけ特別」という情報は表現できません。


典型例2:固定のヘッダー+可変のボディ

「ヘッダー部分は決まっていて、後ろに同じ型が続く」構造

たとえば、「最初の2つは必ず固定情報、そのあとにデータが続く」という配列を考えます。

type DataRow = [id: number, name: string, ...scores: number[]];

const row1: DataRow = [1, "Taro"];              // OK(スコアなし)
const row2: DataRow = [2, "Hanako", 80, 90];    // OK
const row3: DataRow = [3, "Jiro", 70, 60, 50];  // OK
// const ng: DataRow = ["x", "Taro"];           // NG:先頭が number じゃない
TypeScript

ここでは、

  • 1番目は id(number)
  • 2番目は name(string)
  • 3番目以降は scores(number[])

という構造を1つの型で表現しています。
「ヘッダーは固定、ボディは可変」という構造は、現実のデータでもよく出てくるので、
可変長タプルがきれいにハマる場面です。


可変長タプルと普通の配列・タプルとの違い

普通の配列との違い

let xs: (string | number)[] = ["sum", 1, 2, 3];
TypeScript

この型だと、「どの位置も string か number」という情報しかありません。
「先頭は必ず string」という制約は表現できません。

一方、可変長タプルなら、

type Log = [string, ...number[]];
TypeScript

と書くことで、「先頭だけは絶対 string」という仕様を型にできます。
つまり、「一部だけ位置に意味がある配列」を表現できるのが可変長タプルです。

固定長タプルとの違い

固定長タプルは、要素数も完全に決まっています。

type Fixed = [string, number];

const a: Fixed = ["x", 1];   // OK
// const b: Fixed = ["x"];   // NG
// const c: Fixed = ["x", 1, 2]; // NG
TypeScript

可変長タプルは、「ここまでは固定、ここから先は自由」という形です。

type Var = [string, ...number[]];

const v1: Var = ["x"];          // OK
const v2: Var = ["x", 1];       // OK
const v3: Var = ["x", 1, 2, 3]; // OK
TypeScript

「完全に固定」か「一部だけ固定で、残りは伸びる」か——
この違いを意識しておくと、どこで可変長タプルを使うべきかが見えてきます。


可変長タプルを使うときに意識したいこと

「どこまでが“位置に意味がある部分”か」をはっきりさせる

可変長タプルを設計するときに一番大事なのは、
「どこまでが固定のタプル部分で、どこからが“ただの配列”部分か」を自分の中で言語化することです。

たとえば、

  • 1番目:イベント名(string)
  • 2番目以降:パラメータ(number[])

なら [string, ...number[]] だし、

  • 1番目:id(number)
  • 2番目:name(string)
  • 3番目以降:scores(number[])

なら [number, string, ...number[]] になります。

「ここまでは位置に意味がある」「ここから先は同じ型が続くだけ」
この境界線をはっきりさせることが、可変長タプル設計のコアです。

「何でもアリの配列」の代わりに使う

本当は「先頭だけ特別」なのに、なんとなく (string | number)[]any[] にしてしまうと、
型としての情報がかなり失われます。

「先頭は必ずラベル」「そのあとに値が続く」
「最初の2つはメタ情報、そのあとにデータが続く」

こういう構造を見つけたら、一度可変長タプルで表現してみる。
それだけで、「仕様そのものが型に乗る」感覚がかなり強くなります。


まとめ:可変長タプルは「固定タプル+配列」のいいとこ取り

可変長タプルは、

  • タプルの「位置に意味がある」「先頭の型が決まっている」という強さ
  • 配列の「後ろに同じ型がいくつでも続いていい」という柔軟さ

この2つを同時に持った型です。

「ここまでは絶対この形」「ここから先は同じ型が続くだけ」
そんな配列を見つけたら、それは可変長タプルの出番かもしれません。

最初は少しトリッキーに見えるけれど、
一度「先頭だけ特別な配列」を可変長タプルで表現してみると、
「型でここまで言えるのか」という気持ちよさが、かなりはっきり分かるはずです。

タイトルとURLをコピーしました