TypeScript | 関数・クラス・ジェネリクス:関数設計の深化 – rest引数の型指定

TypeScript
スポンサーリンク

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) {} // これはエラー
TypeScript

rest引数は「残り全部」を受け取るので、
最後にしか置けません。


型設計の視点: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;
}
TypeScript

Parameters<typeof add>[number, number] です。

つまり、

const wrapped = withLog(add);
// wrapped の型は (a: number, b: number) => number
TypeScript

withLog の中の

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
TypeScript

onClick の引数 (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[]」ではなく、
関数の意図をそのまま表現できる、強力な型設計の道具 に変わっていきます。

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