「keyof typeof」を使いこなして、TypeScriptの型システムをマスターしよう

2024-04-02

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


TypeScript でつまずきがちな this の落とし穴!Angular 2 コンポーネントで発生する this 未定義問題を完全解決

Angular 2 コンポーネント内で、メソッドを呼び出してコールバック関数を渡す場合、コールバック関数内で this キーワードを使用しようとすると、「this」が未定義になることがあります。これは、コールバック関数がコンテキストの外で実行されるためです。...


TypeScriptでカスタムグローバルインターフェースを設定する方法

手順プロジェクトフォルダ内に*.d.tsファイルを作成します。ファイル名は自由ですが、一般的にはglobals. d.tsなど分かりやすい名前が推奨されます。ファイル内にインターフェースを定義します。インターフェース名は、グローバルスコープで参照できる名前にしてください。...


Angular エラー「Can't bind to 'ngModel' since it isn't a known property of 'input'」の解決方法

このエラーは、Angular テンプレートで ngModel ディレクティブを input 要素にバインドしようとした際に発生します。これは、ngModel が input 要素の既知のプロパティではないためです。原因このエラーの主な原因は以下の2つです。...


【初心者向け】React TypeScriptで「Cannot invoke an object which is possibly 'undefined'.ts(2722)」エラーを分かりやすく解説

このエラーは、TypeScriptコンパイラが、呼び出そうとしているオブジェクトが undefined である可能性があると検出したときに発生します。これは、オブジェクトが初期化されていない、またはnull値に設定されている場合、または条件付きで存在する場合などに起こります。...


React.jsとTypeScriptで発生する"'React' was used before it was defined"エラーの解決方法

このエラーメッセージは、ReactJSプロジェクトでJavaScriptファイル内で React 変数を参照しようとしているが、その変数がまだ定義されていない場合に発生します。原因このエラーが発生する主な原因は以下の2つです。import 文の記述ミス...