rest引数ってそもそも何をしているのか
まずは「rest引数」の正体からいきます。
function sum(...numbers: number[]) {
// numbers は「配列」として受け取る
}
TypeScript...numbers は、「可変長引数」を1つの配列として受け取る仕組みです。
呼び出し側はこう書けます。
sum(1, 2, 3);
sum(10, 20);
sum(); // 0個でもOK
TypeScript関数の中では、numbers は常に number[] です。
つまり、「rest引数は“配列として受け取る引数”であり、その配列の要素型をどう設計するかがポイント」 になります。
基本形:単純な rest引数の型指定
「全部同じ型」の可変長引数
一番シンプルなパターンは、
「同じ型の値をいくつでも受け取りたい」というものです。
function sum(...numbers: number[]) {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3); // OK
sum(10); // OK
sum(); // OK(numbers は [])
TypeScriptここでの型指定は ...numbers: number[]。
これは「number の配列を受け取る」という意味です。
rest引数は「配列として受け取る」ので、
「rest引数の型指定=配列の要素型の指定」 と覚えておくと分かりやすいです。
もう一歩:rest引数と他の引数の組み合わせ
先頭に必須引数+後ろに可変長引数
例えば、「最初の1つは必須、それ以降は任意の数だけ受け取りたい」というケース。
function logFirstAndRest(first: string, ...rest: string[]) {
console.log("first:", first);
console.log("rest:", rest);
}
logFirstAndRest("A"); // rest は []
logFirstAndRest("A", "B", "C"); // rest は ["B", "C"]
TypeScriptここでは、
first: string は単体の文字列rest: string[] は「2つ目以降の文字列の配列」
という関係になっています。
重要なのは、
「rest引数は必ず“最後の引数”である必要がある」 というルールです。
function bad(...rest: string[], last: string) {} // これはエラー
TypeScriptrest引数は「残り全部」を受け取るので、
最後にしか置けません。
型設計の視点:rest引数は「どんな配列として扱いたいか」を決める
単純な T[] だけでなく、タプル型も使える
ここから少し TypeScript らしい話をします。
rest引数の型は、
単なる number[] や string[] だけでなく、
「タプル型」も指定できます。
例えば、「最初の2つは必須で、それ以降は任意」という形を、
rest引数側に寄せて書くこともできます。
function handlePairAndMore(...values: [number, number, ...number[]]) {
const [first, second, ...rest] = values;
console.log("first:", first);
console.log("second:", second);
console.log("rest:", rest);
}
handlePairAndMore(1, 2); // OK
handlePairAndMore(1, 2, 3, 4); // OK
// handlePairAndMore(1); // エラー:最低2つ必要
TypeScript[number, number, ...number[]] は、
「少なくとも2つの number、その後に任意個の number」というタプル型です。
ここでのポイントは、
「rest引数の型は“配列型”だけでなく、“タプル型+rest”という形でも書ける」
ということです。
これを使うと、「最低限必要な個数」と「それ以降の可変部分」を、
1つの rest引数で表現できます。
ジェネリクスと rest引数:柔軟な関数の型を作る
「どんな引数でも受け取って、そのまま別の関数に渡す」例
例えば、「ログを出してから関数を呼ぶ」ラッパー関数を作りたいとします。
function withLog<F extends (...args: any[]) => any>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
console.log("call with:", args);
return fn(...args);
};
}
TypeScriptここで出てきた (...args: Parameters<F>) が、
rest引数+ジェネリクスの組み合わせです。
Parameters<F> は、関数 F の引数の型(タプル)を表します。
例えば、fn がこういう関数だったとします。
function add(a: number, b: number): number {
return a + b;
}
TypeScriptParameters<typeof add> は [number, number] です。
つまり、
const wrapped = withLog(add);
// wrapped の型は (a: number, b: number) => number
TypeScriptwithLog の中の
return (...args: Parameters<F>): ReturnType<F> => {
console.log("call with:", args);
return fn(...args);
};
TypeScriptは、
「fn と同じ引数を rest引数として受け取り、
そのまま fn に渡して、同じ戻り値を返す関数」
という意味になります。
ここでの重要ポイントは、
「rest引数の型に“タプル型(Parameters<F>)”をそのまま渡せる」
ということです。
これによって、
「元の関数とまったく同じ引数リストを持つ関数」を、
型安全に作ることができます。
実務でよくあるパターン:ログ・ラップ・イベントハンドラ
イベントハンドラにそのまま引数を渡す
例えば、イベントハンドラをラップする関数。
type Handler<T extends any[]> = (...args: T) => void;
function withPrefix<T extends any[]>(handler: Handler<T>, prefix: string): Handler<T> {
return (...args: T) => {
console.log(prefix, ...args);
handler(...args);
};
}
TypeScriptここでの T extends any[] は「タプル型(配列型)」を表し、(...args: T) が rest引数です。
例えば、こう使えます。
const onClick = (x: number, y: number) => {
console.log("clicked at", x, y);
};
const loggedOnClick = withPrefix(onClick, "[CLICK]");
loggedOnClick(10, 20);
// [CLICK] 10 20
// clicked at 10 20
TypeScriptonClick の引数 (x: number, y: number) が、T として withPrefix に伝わり、(...args: T) でそのまま受け取って、handler(...args) でそのまま渡しています。
ここでも、
「rest引数の型=“引数リスト全体のタプル型”」
という設計が効いています。
rest引数の型指定で意識しておきたいこと
「この関数は、どんな“配列”として引数を扱いたいのか?」
rest引数は、
見た目は「バラバラの引数を受け取る」ように見えますが、
型の世界では「1つの配列(またはタプル)」として扱われます。
だから、設計のときに考えるべきなのは、
「この関数は、どんな配列(どんなタプル)を受け取る関数なのか?」
という問いです。
単純に「同じ型の値をいくつでも」なら T[]
「最低何個かは必須、その後は任意」ならタプル+...T[]
「別の関数と同じ引数リストをそのまま受け取りたい」なら Parameters<F>
というふうに、
「配列/タプルとしての形」を意識して型を付ける のが、
rest引数設計のコツです。
まとめ:rest引数の型指定を自分の言葉で言うと
最後に、あなた自身の言葉でこう整理してみてください。
rest引数は、
「可変長の引数を、1つの配列(またはタプル)として受け取る仕組み」
だから、型としては「その配列の要素型(またはタプル型)」を指定する。
...args: T[] は「T の配列」...args: [A, B, ...C[]] は「最低 A, B があり、その後に C が続くタプル」...args: Parameters<F> は「関数 F と同じ引数リスト」
コードを書くとき、
「この rest引数は、どんな“配列の形”として扱いたいんだっけ?」
と一度立ち止まってから型を書くようにしてみてください。
その一呼吸で、
rest引数は「とりあえず any[]」ではなく、
関数の意図をそのまま表現できる、強力な型設計の道具 に変わっていきます。
