TypeScript | 基礎文法:配列・タプル – スプレッド構文と型

TypeScript
スポンサーリンク

スプレッド構文とは何か(型の世界でどう見えるか)

スプレッド構文 ... は、配列やオブジェクトの「中身を展開する」ための記法です。
配列なら「要素をバラして別の配列に入れ直す」、オブジェクトなら「プロパティをコピーする」イメージです。

const a = [1, 2, 3];
const b = [...a, 4, 5]; // [1, 2, 3, 4, 5]
TypeScript

JavaScript的には「展開」ですが、TypeScript的には「展開した結果の型をどう推論するか」がポイントになります。


配列に対するスプレッド構文と型推論

単純な配列のコピー・結合

const a = [1, 2, 3];          // number[]
const b = [4, 5];             // number[]
const c = [...a, ...b];       // number[]
TypeScript

abnumber[] なので、cnumber[] と推論されます。
「同じ型の配列同士をスプレッドでつなぐと、同じ型の配列になる」という素直な挙動です。

異なる型を混ぜると、要素型はユニオンになります。

const a = [1, 2];        // number[]
const b = ["a", "b"];    // string[]
const c = [...a, ...b];  // (number | string)[]
TypeScript

TypeScriptは「number と string が混ざった配列」として扱います。

先頭にリテラルを足したときの型

const a = [1, 2, 3];          // number[]
const b = [0, ...a];          // number[]
TypeScript

0number なので、bnumber[] です。
ここでは特にタプルにはならず、「number の配列」として扱われます。


タプルとスプレッド構文(型が一気に面白くなるところ)

タプルをスプレッドすると「位置情報」が保たれる

const t1: [string, number] = ["Taro", 20];
const t2: [boolean] = [true];

const t3 = [...t1, ...t2];
// 型: [string, number, boolean]
TypeScript

ここでは、t1t2 もタプルなので、
スプレッドした結果も「位置ごとの型が決まったタプル」として推論されます。

つまり、「タプル+タプル」をスプレッドでつなぐと、
「位置付きの型情報を保ったまま、長いタプルになる」ということです。

リテラル+タプルの組み合わせ

const t1: [number, number] = [10, 20];

const t2 = [0, ...t1];
// 型: [number, number, number]
TypeScript

ここでも、「先頭の 0(number)+タプル [number, number]」ということで、
結果は [number, number, number] というタプルになります。


可変長タプルとスプレッド構文

型レベルのスプレッド(…T[] をタプルに埋め込む)

可変長タプルは、型の中でスプレッドを使います。

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

const a: Log = ["sum"];          // OK
const b: Log = ["sum", 1, 2, 3]; // OK
TypeScript

ここで ...number[] は、「2番目以降は number が0個以上続いていい」という意味です。
「先頭だけ位置が決まっていて、後ろは配列」という構造を、型で表現しています。

実際のスプレッド構文との相性

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

logSum("total");        // OK
logSum("total", 1, 2);  // OK
TypeScript

ここでは、
呼び出し側:...(実引数のスプレッド)
型側:[string, ...number[]](可変長タプル)

という形で、「スプレッド構文」と「スプレッドを含むタプル型」がきれいに対応しています。


スプレッドで「コピーしてから安全にいじる」型の感覚

readonly配列・タプルからのコピー

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

const ys = [...xs]; // ys: number[]
ys.push(4);         // OK(ys は普通の配列)
TypeScript

xsreadonly number[] ですが、スプレッドでコピーすると新しい number[] になります。
「元は不変のまま」「コピーした方だけ自由に変更」というスタイルが取りやすくなります。

タプルでも同じです。

const t: readonly [string, number] = ["Taro", 20];

const u = [...t]; // u: (string | number)[]
TypeScript

ここでは「タプル → 普通の配列」に崩れますが、
「元を壊さずに、中身だけ取り出して別の配列として扱う」という意図ははっきりしています。


まとめの感覚:スプレッド構文は「型の形を保ったまま広げる」

スプレッド構文は、値の世界では「展開」ですが、
型の世界では「展開した結果の配列・タプルの形をどう保つか」という話になります。

配列同士なら「要素型をマージした配列」
タプル同士なら「位置情報をつなげたタプル」
可変長タプルなら「固定部分+可変部分」をそのまま表現

というふうに、「形」を意識して見ると一気に理解しやすくなります。

コードを書きながら、
「この ... は、型の世界ではどんな形に広がっているんだろう?」
と一瞬だけ立ち止まってみると、スプレッド構文と型の関係がどんどんクリアになっていきます。

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