こんにちは。TypeScriptの深淵へようこそ。
現場でコードを書いていて、「引数の数が決まっていない関数、どう型定義すればいいんだろう?」と悩んだことはありませんか? `any` で逃げてしまうのは簡単ですが、それではTypeScriptの真価である「コンパイル時の静的解析」をドブに捨てるようなものです。
今日は、可変長引数を扱うための強力な武器、Rest Parameters(…args)とタプル型の組み合わせについて、型システムの裏側まで覗き込みながら解説します。ここをマスターすれば、あなたのコードは一段上の「堅牢さ」を手に入れますよ。
—
1. Rest Parametersの基本:まずは「袋」を用意する
関数で「いくつ引数が来るかわからないけれど、全部まとめて受け取りたい」という時、`…args` を使いますよね。
function sum(…numbers: number[]) {
return numbers.reduce((acc, cur) => acc + cur, 0);
}
// 実行
console.log(sum(1, 2, 3)); // 6
これは「`numbers` という名前の袋の中に、`number` 型の要素をいくつでも放り込んでいいよ」という宣言です。非常に直感的ですが、これだけだと「すべての引数が同じ型でなければならない」という制約があります。
「文字列と数値を混ぜて渡したい」「特定の順番で特定の型を保証したい」……そんな贅沢な要求に応えるのが、タプル型との融合です。
—
2. タプル型による「精密な型縛り」
TypeScriptのタプル型は、単なる配列ではなく「位置によって型が決まっている配列」です。これをRest Parametersに適用すると、魔法のような型安全性が手に入ります。
実践:型が混在する関数を定義する
例えば、ログ出力関数を考えてみましょう。`[レベル, メッセージ, コード]` という特定の順番で引数を受け取りたい場合、こう書きます。
type LogArgs = [level: ‘info’ | ‘error’, message: string, code: number];
function log(…args: LogArgs) {
const [level, message, code] = args;
console.log(`[${level.toUpperCase()}] ${message} (Code: ${code})`);
}
// 正しい呼び出し
log(‘info’, ‘サーバーが起動しました’, 200);
// エラーになる呼び出し
// log(‘warning’, ‘未知の警告’, 500);
// 理由: ‘warning’ は ‘info’ | ‘error’ に含まれていない
ここでのポイントは、`…args: LogArgs` と書くことで、引数の数だけでなく、位置ごとの型までもがコンパイラに監視されるという点です。TypeScriptは、この関数が呼ばれた瞬間、渡された引数が `LogArgs` というタプルの形に合致しているかを完璧にチェックします。
—
3. なぜタプルを使うと「強くなる」のか?
初学者が陥りやすいのが、`any[]` を使った甘い設計です。
// 悪い例:型がザル
function badFunction(…args: any[]) { … }
これだと、関数内部で `args[0]` が何型なのかコンパイラは推論できません。しかし、タプル型を使えば:
1. オートコンプリートが効く: エディタが「次は数値を入れる番だよ」と教えてくれる。
2. 安全な分割代入: `const [level, message, code] = args;` と書いた瞬間に、それぞれの変数は厳密な型として推論される。
3. リファクタリング耐性: 引数の順序を変えるだけで、呼び出し側すべてにエラーが出るため、バグの混入を即座に防げる。
まさに「言語仕様を味方につけた開発」ですね。
—
4. 陥りやすい罠:オプショナル引数との組み合わせ
タプル型でオプショナルな要素を扱うことも可能です。例えば「最後のコードは省略できるかも」という場合。
type OptionalArgs = [level: ‘info’ | ‘error’, message: string, code?: number];
function logOptional(…args: OptionalArgs) {
const [level, message, code] = args;
// code は number | undefined になる
console.log(level, message, code ?? ‘N/A’);
}
logOptional(‘info’, ‘テスト’); // OK
ここで注意したいのは、「オプショナル引数の後ろに必須引数は置けない」というJavaScript本来のルールです。タプル型でも `[string, number?, string]` と書くとコンパイルエラーになります。常に「必須項目が先、オプションが後」の原則を守りましょう。
—
最後に:型は「ドキュメント」以上の価値がある
TypeScriptの型定義を書くことは、単にエディタにヒントを与える作業ではありません。「この関数がどうあるべきか」という設計思想をコンパイラという強力なエンジンに刻み込む作業です。
Rest Parametersとタプル型を使いこなせれば、複雑なAPIインターフェースも、型安全なブラックボックスとして実装できるようになります。
「ここをクリアすれば、TypeScriptの基本はバッチリ」と言いましたが、実はこれがTypeScriptの応用力の入り口です。ぜひ、今日からあなたの書く関数に、`any` ではなく、タプルによる「型のお守り」を添えてみてください。
あなたのコードが、より洗練されたものになることを応援しています!