「keyof typeof」を使いこなして、TypeScriptの型システムをマスターしよう
TypeScriptにおける「keyof typeof」の意味
基本的な仕組み
- 「keyof」:オブジェクトのプロパティ名を取得
- 「typeof」:変数や型の型情報を取得
**「keyof typeof」**は、これらの2つの演算子を組み合わせることで、オブジェクトの型情報からプロパティ名のみを取り出すことができます。
例:
interface User {
name: string;
age: number;
}
const user: User = {
name: "John Doe",
age: 30,
};
// keyof User で User インターフェースのプロパティ名を取得
type UserKeys = keyof User; // "name" | "age"
// typeof user で User 変数の型情報(User インターフェース)を取得
type UserType = typeof user; // User
// keyof typeof user で User 変数の型情報からプロパティ名のみを取り出す
type UserPropertyKeys = keyof typeof user; // "name" | "age"
具体的なユースケース
オブジェクト型ガード
function isUser(obj: any): obj is User {
// keyof typeof User で User インターフェースのプロパティ名を取得
const userKeys: string[] = ["name", "age"];
// オブジェクトのすべてのキーが User インターフェースのプロパティ名に含まれているかどうかをチェック
return userKeys.every((key) => key in obj);
}
const person: any = {
name: "John Doe",
age: 30,
// 存在しないプロパティ
address: "123 Main Street",
};
if (isUser(person)) {
// person は User 型であることが保証される
console.log(`User: ${person.name}`);
} else {
console.log("Not a user");
}
型安全なプロパティへのアクセス
function getProperty(obj: User, key: keyof User): any {
// keyof typeof User で User インターフェースのプロパティ名のみを取り出す
const userKeys: string[] = ["name", "age"];
// key が User インターフェースのプロパティ名であることが保証される
if (userKeys.includes(key)) {
return obj[key];
} else {
throw new Error(`Invalid property key: ${key}`);
}
}
const name = getProperty(user, "name"); // "John Doe"
const age = getProperty(user, "age"); // 30
// 存在しないプロパティへのアクセスはエラーとなる
// getProperty(user, "address"); // エラー: Invalid property key: address
その他のユースケース
- ジェネリック型
- discriminated union
- 型マッピング
まとめ
**「keyof typeof」**は、TypeScriptの型システムを最大限に活用するための強力なツールです。理解と使い慣れによって、コードの表現力、安全性、保守性を大幅に向上させることができます。
interface User {
name: string;
age: number;
}
function isUser(obj: any): obj is User {
// keyof typeof User で User インターフェースのプロパティ名を取得
const userKeys: string[] = ["name", "age"];
// オブジェクトのすべてのキーが User インターフェースのプロパティ名に含まれているかどうかをチェック
return userKeys.every((key) => key in obj);
}
const person: any = {
name: "John Doe",
age: 30,
// 存在しないプロパティ
address: "123 Main Street",
};
if (isUser(person)) {
// person は User 型であることが保証される
console.log(`User: ${person.name}`);
} else {
console.log("Not a user");
}
interface User {
name: string;
age: number;
}
function getProperty(obj: User, key: keyof User): any {
// keyof typeof User で User インターフェースのプロパティ名のみを取り出す
const userKeys: string[] = ["name", "age"];
// key が User インターフェースのプロパティ名であることが保証される
if (userKeys.includes(key)) {
return obj[key];
} else {
throw new Error(`Invalid property key: ${key}`);
}
}
const user: User = {
name: "John Doe",
age: 30,
};
const name = getProperty(user, "name"); // "John Doe"
const age = getProperty(user, "age"); // 30
// 存在しないプロパティへのアクセスはエラーとなる
// getProperty(user, "address"); // エラー: Invalid property key: address
interface User {
name: string;
age: number;
}
function getPropertyValue<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = {
name: "John Doe",
age: 30,
};
const name = getPropertyValue(user, "name"); // "John Doe"
const age = getPropertyValue(user, "age"); // 30
interface User {
type: "user";
name: string;
age: number;
}
interface Admin {
type: "admin";
email: string;
permissions: string[];
}
type UserOrAdmin = User | Admin;
function getUserInfo(user: UserOrAdmin): string {
switch (user.type) {
case "user":
return `Name: ${user.name}, Age: ${user.age}`;
case "admin":
return `Email: ${user.email}, Permissions: ${user.permissions.join(", ")}`;
}
}
const user: User = {
type: "user",
name: "John Doe",
age: 30,
};
const admin: Admin = {
type: "admin",
email: "[email protected]",
permissions: ["read", "write", "delete"],
};
console.log(getUserInfo(user)); // Name: John Doe, Age: 30
console.log(getUserInfo(admin)); // Email: [email protected], Permissions: read, write, delete
interface User {
name: string;
age: number;
}
type UserReadonly = {
readonly [K in keyof User]: User[K];
};
const user: User = {
name: "John Doe",
age: 30,
};
const readonlyUser: UserReadonly = {
name: user.name,
age: user.age,
};
// readonlyUser.name = "Jane Doe"; // エラー: readonly property
これらのサンプルコードは、「keyof typeof」のさまざまなユースケースを理解するのに役立つでしょう。
「keyof typeof」の代替方法
型ガード
interface User {
name: string;
age: number;
}
function isUser(obj: any): obj is User {
return "name" in obj && "age" in obj;
}
const person: any = {
name: "John Doe",
age: 30,
};
if (isUser(person)) {
// person は User 型であることが保証される
console.log(`User: ${person.name}`);
} else {
console.log("Not a user");
}
型アサーション
interface User {
name: string;
age: number;
}
const user = {
name: "John Doe",
age: 30,
} as User;
console.log(user.name); // John Doe
インデックス型
interface User {
name: string;
age: number;
}
const user: User = {
name: "John Doe",
age: 30,
};
const name = user["name"]; // "John Doe"
const age = user["age"]; // 30
// 存在しないプロパティへのアクセスはエラーとなる
// const address = user["address"]; // エラー: Property 'address' does not exist on type 'User'.
これらの代替方法は、「keyof typeof」よりも簡潔な場合もありますが、型安全性やコードの読みやすさにおいて劣る場合もあります。
「keyof typeof」を使用するべき状況
- 型安全性を重視する場合
- コードの読みやすさを向上させたい場合
- ジェネリック型や discriminated union など、より高度な機能を利用する場合
- コードの簡潔さを重視する場合
状況に応じて適切な方法を選択することが重要です。
「keyof typeof」は、TypeScriptの型システムを活用するための強力なツールです。しかし、状況によっては代替方法の方が適している場合もあります。それぞれの方法の特徴を理解し、適切な方法を選択することが重要です。
typescript typeof union-types