【入門編】 ConstructorParametersによるコンストラクタ引数の型取得 – TypeScript実践ガイド

TypeScriptの世界へようこそ!チーフアーキテクトの私です。

日々のフロントエンド開発、本当にお疲れ様です。JavaScriptの「型がない不安感」を解消したくてTypeScriptを使い始めたものの、次から次へと出てくる見慣れない記号やユーティリティ型に「うわ、また難しそうなの出てきた……」と、そっとエディタを閉じたくなる瞬間、ありますよね。

大丈夫ですよ、安心してください。今日一緒に見ていく `ConstructorParameters` も、名前こそ呪文のようにいかついですが、仕組みがわかれば「なーんだ、めちゃくちゃ便利じゃん!」とあなたの強い味方になってくれる相棒です。

今日は、身近なたとえ話を交えながら、この子と仲良くなっていきましょう。

—

1. コンストラクタ引数の型取得って、一体なに?

まずは「コンストラクタ」ってなんだっけ?というところから整理しましょう。

オブジェクト指向のプログラミングでは、クラス(設計図)から新しいオブジェクト(実物)を作りますよね。その際、「最初に材料を受け取って、初期化する係」のメソッドがコンストラクタ(Constructor)です。

例えば、あなたが「お弁当屋さん」を営んでいるとしましょう。
お弁当を作るための「お弁当箱クラス」があるとします。

class BentoBox {
constructor(public mainDish: string, public price: number) {
// メインのおかずと値段を決めて、お弁当箱を組み立てる!
}
}

この「お弁当箱クラス」を新しく作るとき(インスタンス化するとき)、私たちは `new BentoBox(‘唐揚げ’, 500)` のように、メインのおかずと値段という「引数(材料)」を渡します。

さて、ここで問題です。
「このお弁当箱クラスが受け取る材料の型(種類)を、別の場所で自動的に知りたい!」と思ったとき、どうすればいいでしょうか?

「わざわざ手動で書き直せばいいじゃん」って思いますよね? でも、もし元のクラスの材料が「やっぱり副菜も追加しよう!」と変更になったらどうでしょう。あちこちのコードを書き直すのは、ヒューマンエラーの元だし、何より面倒くさいですよね。

そこで登場するのが、TypeScriptの魔法の杖 `ConstructorParameters` です。

—

2. 身近なたとえ話:「オーダー票の複写機」

`ConstructorParameters` をイメージしやすくするために、「オーダー票の複写機(カーボン紙)」にたとえてみましょう。

高級レストランの厨房を思い浮かべてください。
お客さんが「ハンバーグ(200g)、ソースはデミグラス、焼き加減はミディアム」と注文しました。これが「コンストラクタに渡す引数」です。

ホール係が書いたそのオーダー票を、そのまま厨房に渡したり、レジにコピーしたりする必要がありますよね。

`ConstructorParameters` はまさにこの複写機です。
「クラスの設計図(T)をポイッと差し込むと、そのクラスのコンストラクタが受け取る『注文の品々(引数のリスト)』を、そのままタプル型(配列のようなもの)としてコピーして返してくれる」という働きをします。

—

3. 基本の使い方を見てみよう

百聞は一見にしかず。実際のコードを見てみましょう。
エディタを開いている方は、ぜひ手を動かして試してみてくださいね。

// 1. ユーザーを作成するクラスの設計図
class User {
constructor(
public name: string,
public age: number,
public isPremium: boolean
) {}
}

// 2. 「Userクラスのコンストラクタ引数」をConstructorParametersでごっそり取得!
// これにより、 type UserParams = [string, number, boolean] というタプル型が自動で作られます。
type UserParams = ConstructorParameters;

// 3. 実際に使ってみる
// 関数の引数として、Userクラスのコンストラクタに渡す用のデータを綺麗に受け取れるようになります
const createUser = (…args: UserParams): User => {
console.log(‘ユーザーを作成します!’);
return new User(…args); // スプレッド構文でそのまま渡せる!気持ちいい!
};

// 実行してみる
const tanaka = createUser(‘田中さん’, 28, true);

どうでしょう? `UserParams` という型を自分で `[string, number, boolean]` と手書きしなくて済んでいます。もし将来、`User` クラスに `email: string` が追加されても、`UserParams` は自動的にそれに追従してくれます。これぞ型安全の醍醐味ですね!

—

4. どんなときに使うの?(実務でのユースケース)

「理屈は分かったけど、実際どんな時に使うの?」という疑問が湧くと思います。
フロントエンドの現場では、主に「クラスのインスタンス化をラップ(包み隠す)する関数や、DI(依存性の注入)コンテナのような仕組みを作る時」に真価を発揮します。

例えば、「ログを記録しながらクラスを生成する便利関数」を作りたいとしましょう。

// データベースに接続するクラス
class DatabaseConnection {
constructor(public host: string, public timeout: number) {
// 接続処理…
}
}

// ラッパー関数:クラスを作る前後に、共通の処理(ログ出力など)を挟みたい!
function createWithLogging any>(
TargetClass: T,
…args: ConstructorParameters
): InstanceType {
console.log(`[LOG]: ${TargetClass.name} を初期化します…`);

// クラスをインスタンス化して返す
return new TargetClass(…args);
}

// 使うときは、型推論が完璧に効くのでめちゃくちゃ安全!
const db = createWithLogging(DatabaseConnection, ‘localhost’, 3000);
// ❌ もし引数を間違えると、TypeScriptが赤く波線を引いて怒ってくれます!
// const wrongDb = createWithLogging(DatabaseConnection, ‘localhost’, ‘3000秒’); // エラー!

このコード、ちょっと高度に見えるかもしれませんが、やっていることはシンプルです。
「どんなクラスが来ても、そのコンストラクタの引数の型を `ConstructorParameters` で正確に読み取って、ラッパー関数側でも型ミスが起きないようにがっちりガードしている」というわけです。

—

5. つまずきやすいポイントと「大丈夫」のメッセージ

ここで、初学者の方向けにつまずきやすいポイントをひとつ。

`ConstructorParameters` にクラスを渡すとき、そのまま `User` と書くのではなく、`typeof User` と書く必要があります。

  • `User` = クラスから作られた「インスタンス(実物)」の型を指します。
  • `typeof User` = クラスそのもの(設計図)の型を指します。

「あれ?なんかエラーになるぞ?」と思ったときは、大体この `typeof` の付け忘れが原因だったりします。もしエラーが出ても、「あ、設計図の型を渡さなきゃいけないんだっけ!」と思い出して、そっと `typeof` をつけてあげてくださいね。エラーはあなたを攻撃しているのではなく、優しく教えてくれているだけですから。

—

おわりに

今回は `ConstructorParameters` について、少しだけ裏側の仕組みを覗いてみました。

最初は「なんだこの呪文みたいな名前は……」と思ったかもしれませんが、要するに「クラスのコンストラクタの引数リストを自動でコピーしてくる便利な型」です。

実務で毎日使うような超頻出の機能ではないかもしれませんが、ライブラリのコードを読んだり、少し複雑なカスタムフックやヘルパー関数を作ったりするときに、知っていると「おっ、こいつデキるな!」と思われること間違いなしの知識です。

焦らず、自分のペースで、一つずつエディタで試しながらモノにしていってくださいね。
あなたのTypeScriptライフが、より快適で楽しいものになりますように! チーフアーキテクトの私でした。また次の記事でお会いしましょう!

コメント

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