こんにちは!TypeScriptの世界へようこそ。
フロントエンドからバックエンドまで、型安全で頑健なアプリケーションを作る上で、TypeScriptは最高の相棒になってくれますよね。
今回は、TypeScriptの型システムの奥深さと美しさを実感できるテーマ、「タプル型を用いた可変長引数の型安全なラップ関数」についてお話しします。
「なんだか難しそうな名前だな…」と思いましたか?大丈夫です。ここをクリアすれば、あなたもTypeScriptの基本を完全にマスターし、ワンランク上のエンジニアへとステップアップできますよ。さあ、一緒に紐解いていきましょう!
—
1. そもそも「タプル型」と「可変長引数」ってなに?
まずは、今回使う2つの重要パーツのおさらいから始めましょう。
タプル型(Tuple)とは?
配列(Array)の強化版です。通常の配列 `string[]` が「何個でも文字列が入る」のに対し、タプル型は「要素の順番とそれぞれの型が厳密に決まった配列」を指します。
例えば、`[string, number]` なら、1番目が文字列で、2番目が数値であるペアを意味します。
可変長引数(Rest Parameters)とは?
JavaScriptでもおなじみの `…args` のことです。関数に何個引数を渡しても、それを1つの配列として受け取れる便利な構文ですね。
この2つを組み合わせると、「関数に渡された引数の型と数を、一言一句狂わずそのまま別の関数に引き渡す」という、魔法のような型安全の仕組みが作れるんです。
—
2. 実践!型安全なロガーを作ってみよう
イメージしやすいように、具体的なコードを見ていきましょう。
例えば、受け取った引数をそのままコンソールに出しつつ、実行時間を計測するラッパー関数を作りたいとします。
まずは、TypeScriptの型推論の力を借りて、最もエレガントに書いたコードがこちらです。
/
- 任意の関数をラップして、実行前後にログを出力する高階関数
/
function withLogging
fn: T
): (…args: Parameters
// 返すのは、元の関数と同じ引数・同じ戻り値を持つ新しい関数
return function (…args: Parameters
console.log(`[LOG] 引数:`, args);
// 元の関数を実行(Spread syntaxで引数を展開)
const result = fn(…args);
console.log(`[LOG] 戻り値:`, result);
return result;
};
}
このコードの何がスゴいのか?
ここで使われている `Parameters
1. `Parameters
関数型 `T` が受け取る引数の型を、丸ごとタプル型として抽出します。
(例:`fn(name: string, age: number)` なら `[string, number]` になる)
2. `ReturnType
関数型 `T` が返す戻り値の型を抽出します。
これらを組み合わせることで、`withLogging` に渡された関数が「どんな引数をいくつ取ろうとも」、ラッパー関数側も自動的に完全に同じ引数・同じ戻り値の型を維持してくれるのです。
実際に使ってみましょう。
// 具体的な関数を定義
const createUser = (name: string, age: number, isAdmin: boolean) => {
return { id: Math.random(), name, age, isAdmin };
};
// ラッパー関数で包む!
// ここで、wrappedCreateUser の型は自動的に (name: string, age: number, isAdmin: boolean) => 戻り値型 になります。
const wrappedCreateUser = withLogging(createUser);
// 使う側では、完全に型補完が効き、間違った型を入れればコンパイルエラーになります
const user = wrappedCreateUser(“Taro”, 25, false);
// ❌ 怒られる例:第2引数は number なのに string を渡そうとした場合
// wrappedCreateUser(“Jiro”, “twenty”, true);
// ➡️ Error: Argument of type ‘string’ is not assignable to parameter of type ‘number’.
どうですか?型定義を一つひとつ手書きしなくても、TypeScriptのコンパイラが裏側で完璧に型を伝播させてくれるのが分かりますよね。
—
3. 初学者が陥りがちな文法エラーとアンチパターン
このテーマを学習する際、多くの開発者がつまずくポイントがいくつかあります。あらかじめ知っておけば怖くありません!
❌ やりがちエラー 1: `any` の安易な使用
「よく分からないからとりあえず `any` にしておこう」と書くと、TypeScriptの恩恵がすべて消え去ります。
// 良くない例:これでは型安全が完全に崩壊します
function badWrapper(fn: (…args: any[]) => any) {
return (…args: any[]) => {
return fn(…args);
};
}
`any` を使うと、どんな変な引数を渡してもコンパイルエラーにならず、実行時エラーの温床になります。必ずジェネリクス(`
❌ やりがちエラー 2: スプレッド構文とタプル型のミスマッチ
関数を呼び出す際、引数を展開して渡すときにタプル型の推論が崩れることがあります。
// TSのバージョンや書き方によっては、args が単なる配列 (any[]) と認識されてしまうことがある
const fn = (x: number, y: string) => {};
const args = [1, “hello”];
// 引数の型が合わずに怒られることがある
// fn(…args);
このような場合は、配列を `as const`(constアサーション)を使って、「変更不可能なリタラルタプル型」として固定してあげるのが、TypeScriptをうまく手なづけるコツです。
const args = [1, “hello”] as const; // 型は readonly [1, “hello”]
—
まとめ
今回は、タプル型と可変長引数を組み合わせた型安全なラップ関数の作り方について解説しました。
- タプル型を使うことで、配列の「順番と型」を厳密に縛ることができる。
- `Parameters
` などのユーティリティ型を組み合わせることで、関数のシグネチャ(型情報)を動的にコピーできる。 - `any` に頼らず、コンパイラの型推論を信じることで、保守性の高いコードが書ける。
ここをクリアできれば、TypeScriptの「関数型プログラミング」や「高度なライブラリの型定義」を読む・書くための基礎体力がグッと身についています。
「なんだ、TypeScriptってすごく理にかなっていて美しい言語じゃないか!」そう感じてもらえたら嬉しいです。
日々の開発を、より安全に、より楽しくしていきましょう!それではまた次回の記事でお会いしましょう!