ゴール:「typeof Class は“クラスそのものの型”を取る」と腑に落とす
まず一番大事な一文からいきます。
typeof クラス名 は、「インスタンスの型」ではなく「クラスそのもの(コンストラクタ+static)の型」を表す
これが分かると、
- 「
Userとtypeof 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;
TypeScriptUserClassType は、ざっくり言うとこんな型です。
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→ インスタンスの型(nameやgreetなど、インスタンス側のメンバー)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 を直接書いていませんが、
UserやProductを「クラスそのもの」として渡している- その型は「コンストラクタ型(
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 クラスそのものの型”なんだな」
と体で理解できたら、もうこのテーマはほぼクリアです。
