【入門編】 InstanceType – TypeScript実践ガイド

こんにちは!フロントエンドの現場を渡り歩いてきたチーフアーキテクトの私です。

TypeScriptを書き始めると、型定義の海に放り出されて「うっ、なんだこれ…」と息が詰まる瞬間、ありますよね。特に `InstanceType` なんていう、いかにも難しそうな組み込み型を見かけたら、そっとブラウザのタブを閉じたくなる気持ち、痛いほどよく分かります。

でも、大丈夫ですよ。今日ここで、そのモヤモヤをすっきりと解消していきましょう!

—

そもそも `InstanceType` ってなんだろう?

TypeScriptには、あらかじめ用意された便利な型(ユーティリティ型)がたくさんあります。その中のひとつである `InstanceType` は、一言でいうと「設計図(クラス)から、そこから生まれる実物(インスタンス)の型を取り出す魔法の道具」です。

いきなりコードを見ると頭がクラクラしちゃうかもしれないので、身近な例えでお話ししますね。

「たい焼きの型」と「実際のたい焼き」の関係

街のたい焼き屋さんをイメージしてください。
あの中には、鉄製の「たい焼きの金型(フレーム)」がありますよね。あれが、TypeScriptにおけるクラス(コンストラクタ関数)です。

そして、その型に生地とあんこを流し込んで焼き上がった「アツアツのたい焼き」。これが、クラスから作られた「インスタンス(実物)」です。

さて、ここで質問です。
「たい焼きの型(設計図)」そのものを食べることはできませんよね?私たちが食べたいのは、あくまでそこから生まれた「アツアツのたい焼き(実物)」です。

プログラムの世界でも同じようなことが起き合います。「クラスそのものの型」ではなく、「そのクラスから作られるオブジェクト(インスタンス)の型」だけが欲しい!という場面がよくあります。

そんなとき、「この設計図から生まれるのって、要するにどんな実物だっけ?」と逆算して抽出してくれるのが、`InstanceType` なんです。

—

コードで見てみよう!

百聞は一見にしかず。実際のコードを見てみましょう。
エディタを開いて、こんなコードを書いてみてください。

// カフェの「コーヒーメーカー」クラス(これが設計図=コンストラクタ関数です)
class CoffeeMaker {
brand: string;

constructor(brand: string) {
this.brand = brand;
}

brew() {
console.log(`${this.brand}で美味しいコーヒーを淹れます。`);
}
}

// 1. まず、クラスそのものの型を取得してみる(typeof演算子を使います)
type CoffeeMakerClass = typeof CoffeeMaker;

// 2. 「CoffeeMakerClass(設計図)」から、InstanceTypeを使って「実物(インスタンス)の型」を取り出す!
type MyCoffee = InstanceType;

// 検証してみましょう
// MyCoffee は、実物である CoffeeMaker のインスタンス型になります。
const myMachine: MyCoffee = new CoffeeMaker(‘DeLonghi’);

myMachine.brew(); // 無事に動きます!

ここで「あれっ?」と思ったあなた、大正解です。
「普通に `const myMachine: CoffeeMaker = …` って書けばよくない?」って思いませんでしたか?

そう、自分で書いたクラスなら、直接そのクラス名を型として使えばいいんです。
じゃあ一体、わざわざ `InstanceType` なんて回りくどいものを使うのはどんな時なのでしょうか?

—

どんなときに `InstanceType` が救世主になるの?

実務でTypeScriptを書いていると、「いま手元にあるのは、クラスそのもの(設計図)であって、実物の型じゃないんだよなぁ」というジレンマにぶつかります。

例えば、以下のようなケースです。

1. 関数に「クラスの設計図」を引数として渡すとき
2. フレームワーク内部で、動的にインスタンスを生成する処理を書くとき

「設計図を渡された関数の中で、その設計図から生まれた実物の型をビシッと推論させたい!」という玄人好みのシーンで、`InstanceType` はものすごい真価を発揮します。

ちょっと実践的な例を見てみましょう。

// どんなクラスでも受け取って、それを新しくインスタンス化して返すヘルパー関数を作りたい!
function createInstance any>(
Ctor: T
): InstanceType {
// 渡されたコンストラクタ(Ctor)を使って新しいインスタンスを生成する
return new Ctor();
}

class User {
name = ‘Yamada’;
}

class Admin {
role = ‘SuperUser’;
}

// Userの「設計図」を渡すと、返り値の型は自動的に「Userのインスタンス」になる!
const userInstance = createInstance(User);
// userInstance の型は User になります(Yamadaさんこんにちは!)

// Adminの「設計図」を渡すと、返り値の型は自動的に「Adminのインスタンス」になる!
const adminInstance = createInstance(Admin);
// adminInstance の型は Admin になります

なんだか急にプロっぽくてカッコいいコードになりましたね!
この `T extends new (…args: any[]) => any` という部分が「クラスの設計図だよ」という目印で、そこから `InstanceType` が「じゃあ、そこから生まれる実物の型を返すね!」と型をピタッと合わせてくれているのです。

初学者のうちは、この複雑な書き方を完璧に暗記しなくても全然大丈夫です。「あ、クラスの設計図から実物の型を取り出したいときに使うんだな」というイメージさえ持っていれば、いつか必ず「あ、今の場面はアレだ!」とパズルのピースがハマる日が来ます。

—

つまずきやすいポイントと、そっと寄り添うアドバイス

ここで、初心者の人がよくハマりがちな罠をこっそりシェアしておきますね。

  • 罠:「クラス名」と「インスタンスの型」をごちゃ混ぜにしてしまう

TypeScriptでは、`User` と書いたとき、それが「クラス(設計図)そのもの」を指しているのか、「そのクラスから作られたオブジェクトの型」を指しているのか、文脈によって少し混乱します。

  • `const u: User` のときの `User` は、実物の型を指しています。
  • `typeof User` のときの `User` は、クラスそのもの(コンストラクタ)を指しています。

もしエラーが出たら、「あ、今私は設計図を渡したいんだっけ?それとも実物が欲しいんだっけ?」と、頭の中でたい焼きの型を思い浮かべて整理してみてください。

まとめ

  • `InstanceType` は、クラス(設計図)から、そこから生まれるインスタンス(実物)の型を抜き出すためのユーティリティ型。
  • 日常的なコードでは直接使わなくても、汎用的な関数やライブラリの型定義を覗くとめちゃくちゃ頻出する。
  • 難しく考えず、「設計図から実物の型を逆算する便利ツール」とだけ覚えておこう!

TypeScriptの型システムは、最初は厳しくて冷たく感じるかもしれませんが、あなたのコードの安全性を後ろからそっと支えてくれる心強い相棒です。分からないことが出てきても、一つひとつ分解していけば必ず理解できますから、自分のペースで楽しく進めていきましょうね!

コメント

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