可変長タプルとは何か(「一部だけ長さが伸びるタプル」)
タプルは本来「要素数が固定」の型ですが、
「ここまでは固定、それ以降は同じ型がいくつでも続いていい」
という形を表現できるのが「可変長タプル(可変長タプル型)」です。
ざっくり言うと、
- 先頭の何個かは「位置と型が決まっている」
- そのあとに「同じ型の要素が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つを同時に持った型です。
「ここまでは絶対この形」「ここから先は同じ型が続くだけ」
そんな配列を見つけたら、それは可変長タプルの出番かもしれません。
最初は少しトリッキーに見えるけれど、
一度「先頭だけ特別な配列」を可変長タプルで表現してみると、
「型でここまで言えるのか」という気持ちよさが、かなりはっきり分かるはずです。
