TypeScript | 関数・クラス・ジェネリクス:クラス設計 – typeof Class の意味

TypeScript TypeScript
スポンサーリンク

ゴール:「typeof Class は“クラスそのものの型”を取る」と腑に落とす

まず一番大事な一文からいきます。

typeof クラス名 は、「インスタンスの型」ではなく「クラスそのもの(コンストラクタ+static)の型」を表す

これが分かると、

  • Usertypeof User の違い」
  • 「static メンバーを持つクラスの型をどう書くか」
  • 「クラスを引数に取る関数の型」

が一気にスッキリします。


まずは前提:「クラスには2つの顔がある」

インスタンスの型としての顔

シンプルなクラスから始めます。

class User {
  constructor(public name: string) {}

  greet(): void {
    console.log(`こんにちは、${this.name}です`);
  }
}

const u = new User("Taro");

function printUser(user: User): void {
  console.log(user.name);
  user.greet();
}

printUser(u);
TypeScript

ここでの User は「インスタンスの型」です。

つまり、

  • name: string を持っていて
  • greet(): void を呼べる

ようなオブジェクトの型を表しています。

この「インスタンスの型としての User」は、
クラスの“中身の構造”を表していると思ってください。

値(コンストラクタ)としての顔

同じ User でも、こう書くときは「値」として扱っています。

const C = User;
const u2 = new C("Hanako");
TypeScript

ここでの User は、

new (name: string) できるコンストラクタという“値”

です。

この「クラス=コンストラクタという値」の型を取りたいときに出てくるのが
typeof User です。


typeof Class の基本的な意味

typeof User は「User クラスそのものの型」

さっきの User を使って、typeof を見てみます。

class User {
  constructor(public name: string) {}

  static fromId(id: number): User {
    return new User(`user-${id}`);
  }
}
TypeScript

ここで、

  • User → インスタンスの型としても使える
  • User という“値” → コンストラクタ+static メソッドを持つオブジェクト

になっています。

typeof User は、この「User という値」の型を取ります。

type UserClassType = typeof User;
TypeScript

UserClassType は、ざっくり言うとこんな型です。

type UserClassType = {
  new (name: string): User;   // コンストラクタ
  fromId(id: number): User;   // static メソッド
};
TypeScript

つまり、

typeof User は「new できて、fromId という static メソッドを持つ“クラスの型”」

という意味になります。

User と typeof User を並べて比べる

整理するとこうです。

class User {
  constructor(public name: string) {}

  static fromId(id: number): User {
    return new User(`user-${id}`);
  }
}

// インスタンスの型
let u: User;

// クラスそのものの型
let UC: typeof User;

UC = User;              // OK
u = new UC("Taro");     // OK
const u2 = UC.fromId(1);
TypeScript

ここでのポイントは、

  • User → インスタンスの型(namegreet など、インスタンス側のメンバー)
  • typeof User → クラスの型(コンストラクタ+static メンバー)

という役割分担になっていることです。


実用パターン1:クラスを引数に取る関数の型

「クラスそのもの」を受け取りたいとき

例えば、「User クラスを受け取って、そこからインスタンスを作る関数」を考えます。

class User {
  constructor(public name: string) {}

  static fromId(id: number): User {
    return new User(`user-${id}`);
  }
}

function createUserById(UserClass: typeof User, id: number): User {
  return UserClass.fromId(id);
}

const u = createUserById(User, 10);
TypeScript

ここでの UserClass: typeof User がまさに、

「User クラスそのもの(コンストラクタ+static)を受け取る」

という型指定です。

もしここを User にしてしまうと、

function createUserById(UserClass: User, id: number): User {
  // UserClass.fromId(...) は呼べない(インスタンスには static がない)
}
TypeScript

というおかしなことになります。

「インスタンスを受け取りたいのか、クラスを受け取りたいのか」

を型で区別するときに、typeof Class が効いてきます。


実用パターン2:ジェネリクス+typeof で「どんなクラスでも受け取る」

コンストラクタを受け取る汎用関数

「どんなクラスでも受け取って、インスタンスを作る関数」を
ジェネリクスで書くときにも、typeof 的な考え方が出てきます。

class User {
  constructor(public name: string) {}
}

class Product {
  constructor(public id: number) {}
}

function createInstance<C extends new (...args: any[]) => any>(
  Ctor: C,
  ...args: ConstructorParameters<C>
): InstanceType<C> {
  return new Ctor(...args);
}

const u = createInstance(User, "Taro");
const p = createInstance(Product, 123);
TypeScript

ここでは typeof を直接書いていませんが、

  • UserProduct を「クラスそのもの」として渡している
  • その型は「コンストラクタ型(new (...args) => ...)」

という意味で、typeof Class と同じ“クラス側の型”を扱っています。

typeof User を理解しておくと、
こういう「コンストラクタ型」を見るときの抵抗感がかなり減ります。


実用パターン3:keyof typeof と組み合わせる(定数オブジェクトでよく使う)

定数オブジェクトでの typeof と同じノリでクラスにも使える

クラスに限らず、typeof は「値から型を取る」ためによく使われます。

const COLORS = {
  red: "#f00",
  blue: "#00f",
} as const;

type ColorName = keyof typeof COLORS;
// "red" | "blue"
TypeScript

クラスの場合も同じで、

class Config {
  static readonly ENV = {
    dev: "development",
    prod: "production",
  } as const;
}

type EnvName = keyof typeof Config.ENV;
// "dev" | "prod"
TypeScript

ここでは、

  • Config.ENV は「値」
  • typeof Config.ENV は「その値の型」
  • keyof typeof Config.ENV は「そのキーの union 型」

という流れです。

クラスに対しても、

typeof は“値側”から“型側”を取り出す」

という一貫した動きをしている、という感覚を持っておくと理解しやすくなります。


まとめ:typeof Class を自分の言葉で説明すると

最後に、あなた自身の言葉でこう整理してみてください。

  • User は「インスタンスの型」として使う(user: User など)
  • User という“値”は、コンストラクタ+static メンバーを持つオブジェクト
  • typeof User は、その「クラスという値」の型(コンストラクタ+static 側)を表す
  • クラスそのものを引数に取りたいときや、static メンバーを使いたいときは typeof Class を使う
  • typeof は「値から型を取る」キーワードであり、クラスにもオブジェクトにも同じように使える

まずは、

  • 「インスタンスを受け取る関数」(引数の型は User
  • 「クラスを受け取る関数」(引数の型は typeof User

を自分で 1 つずつ書き分けてみてください。

そこで、

「あ、typeof User は“User クラスそのものの型”なんだな」

と体で理解できたら、もうこのテーマはほぼクリアです。

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