はじめてのTypeScriptプログラミング入門 30日コース | Generics・Utility Types・Class・Module - Day 25:keyof・typeof

TypeScript 30日で身につけるTypeScript
スポンサーリンク
スポンサーリンク
  1. Day 25:keyof・typeofで「型安全なプロパティアクセス」を手に入れる
  2. keyofとは何か:型から「プロパティ名の集合」を取り出す
    1. keyofの基本イメージ
    2. keyofを使うと何がうれしいか
  3. typeofとは何か:値から「その型」を取り出す
    1. typeofの基本イメージ(TypeScript版)
  4. keyof typeofの組み合わせ:値から「キーの集合」を取り出す
  5. 型安全なプロパティアクセスとは何か
  6. いよいよ本題:getValue<T, K extends keyof T> を分解する
    1. まずは型パラメータTとKを理解する
    2. 引数の型を読み解く
    3. 戻り値の型 T[K] を理解する
  7. getValueを具体的な型で試してみる
    1. User型で使ってみる
    2. 間違ったキーを渡すとどうなるか
  8. getValueを別の型でも試してみる
    1. Product型で使う
  9. Day 25 練習テンプレート:keyof・typeof・getValueをまとめて試す
  10. Day 25のまとめ
  11. Day 25:Objectから「安全に値を取得する関数」を作る
  12. keyofで「そのObjectが持つキーの集合」を型にする
    1. まずはkeyofの感覚をつかむ
  13. typeofで「値から型を取り出す」
    1. typeofの型的な使い方
  14. keyof typeofで「実際のObjectからキーの集合を型にする」
  15. 型安全なプロパティアクセスのゴールイメージ
  16. Step 1:TとKの役割を理解する
    1. T:オブジェクト全体の型
    2. K extends keyof T:Tのキーだけを許可する
  17. Step 2:引数と戻り値の型を読み解く
    1. 引数の型
    2. 戻り値の型 T[K]
  18. Step 3:Objectから安全に値を取得する関数を実際に使ってみる
    1. Userで試す
    2. Configで試す(keyof typeofの組み合わせ)
  19. Step 4:少し応用して「安全な設定アクセス関数」を作る
  20. Day 25 練習テンプレート:Objectから安全に値を取得する関数
  21. Day 25演習のまとめ

Day 25:keyof・typeofで「型安全なプロパティアクセス」を手に入れる

Day 25では、TypeScriptの中でも“ちょっと玄人っぽい”けれど、 使いこなせると一気にコードが洗練されるテクニックを扱います。

キーワードはこの4つです。

  • keyof
  • typeof
  • keyof typeof
  • 型安全なプロパティアクセス

そして、実践テーマはこの関数を理解することです。

function getValue<T, K extends keyof T>(
  object: T,
  key: K
): T[K] {
  return object[key];
}
TypeScript

見た瞬間「なんだこれは…」となりがちなこの関数を、 ステップバイステップで分解しながら、 初心者の方でも「なるほど、そういうことか」と思えるところまで持っていきます。

keyofとは何か:型から「プロパティ名の集合」を取り出す

keyofの基本イメージ

keyof は、

「型から、そのプロパティ名の“集合”を取り出す演算子」

です。

まずはシンプルな例から見てみます。

type User = {
  id: number;
  name: string;
  email: string;
};

type UserKeys = keyof User;
TypeScript

このとき、UserKeys は次のような型になります。

// 展開イメージ
type UserKeys = "id" | "name" | "email";
TypeScript

ポイント:

  • keyof User は、「User型のプロパティ名を全部集めたユニオン型」です。
  • つまり、「Userのキーとして使える文字列だけ」を表す型になります。

keyofを使うと何がうれしいか

たとえば、次のような関数を考えます。

function printUserKey(key: keyof User) {
  console.log("Userのキー:", key);
}
TypeScript

この関数に渡せるのは、

printUserKey("id");    // OK
printUserKey("name");  // OK
printUserKey("email"); // OK

printUserKey("age");   // コンパイルエラー:Userにageはない
TypeScript

ここが大事なところです:

  • keyof User を使うことで、 「Userに存在しないプロパティ名」をコンパイル時に弾けます。
  • つまり、「キーのスペルミス」や「存在しないプロパティへのアクセス」を、 型レベルで防げるようになります。

typeofとは何か:値から「その型」を取り出す

typeofの基本イメージ(TypeScript版)

JavaScriptの typeof は「実行時に値の型を文字列で返す」演算子ですが、 TypeScriptの型の世界で使う typeof は少し違います。

TypeScriptの typeof は、

「変数や定数から、その型を取り出す演算子」

として使えます。

const user = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

type UserFromConst = typeof user;
TypeScript

このとき、UserFromConst は次のような型になります。

// 展開イメージ
type UserFromConst = {
  id: number;
  name: string;
  email: string;
};
TypeScript

ポイント:

  • typeof user は、「userという変数の型」をそのまま型として取り出します。
  • これにより、「実際のオブジェクトから型を作る」ことができます。

keyof typeofの組み合わせ:値から「キーの集合」を取り出す

ここまでの2つを組み合わせると、 keyof typeof という形が出てきます。

const config = {
  apiUrl: "https://example.com",
  timeout: 5000,
  retry: 3,
};

type ConfigKeys = keyof typeof config;
TypeScript

このとき、ConfigKeys は次のような型になります。

// 展開イメージ
type ConfigKeys = "apiUrl" | "timeout" | "retry";
TypeScript

流れを整理すると:

  1. typeof config で「configの型」を取り出す
  2. keyof (typeof config) で「その型のプロパティ名の集合」を取り出す

つまり、

「実際のオブジェクトから、そのキーの集合を型として取り出す」

ことができるわけです。

型安全なプロパティアクセスとは何か

ここまでの話を、「プロパティアクセス」に結びつけてみます。

JavaScriptでは、次のようなコードがよくあります。

const user = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

console.log(user["name"]); // OK
console.log(user["naem"]); // スペルミスだが、実行時まで気づけない
TypeScript

この "naem" のようなスペルミスは、 実行してみるまで気づけないのがつらいところです。

TypeScriptでは、keyof を使うことで、 「存在しないキー」をコンパイル時に弾くことができます。

type User = {
  id: number;
  name: string;
  email: string;
};

function getUserValue(user: User, key: keyof User) {
  return user[key];
}

const u: User = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

getUserValue(u, "name"); // OK
getUserValue(u, "naem"); // コンパイルエラー:存在しないキー
TypeScript

これが 型安全なプロパティアクセス の基本的な考え方です。

いよいよ本題:getValue<T, K extends keyof T> を分解する

ここから、実践テーマの関数をじっくり分解していきます。

function getValue<T, K extends keyof T>(
  object: T,
  key: K
): T[K] {
  return object[key];
}
TypeScript

まずは型パラメータTとKを理解する

  • T
    • 「オブジェクト全体の型」を表すジェネリック型パラメータです。
    • たとえば User や Product などが入ります。
  • K extends keyof T
    • 「Tのプロパティ名のどれか」を表す型パラメータです。
    • keyof T は「Tのプロパティ名の集合」でした。
    • K extends keyof T は、「Kはその集合の一員ですよ」という制約です。

つまり、この関数は、

「T型のオブジェクトと、 Tに存在するプロパティ名だけを受け取る関数」

ということになります。

引数の型を読み解く

object: T,
key: K
TypeScript
  • object は「T型のオブジェクト」
  • key は「Tに存在するプロパティ名のどれか(K)」

という意味です。

戻り値の型 T[K] を理解する

ここが少し難しいですが、とても重要なポイントです。

T[K] は、

「T型のオブジェクトの、キーKに対応するプロパティの型」

という意味になります。

たとえば、次のような型を考えます。

type User = {
  id: number;
  name: string;
  email: string;
};
TypeScript

このとき、

  • User["id"] は number
  • User["name"] は string
  • User["email"] は string

というように、「インデックスアクセス型」として扱えます。

T[K] は、この「インデックスアクセス型」をジェネリクスで一般化したものです。

getValueを具体的な型で試してみる

User型で使ってみる

type User = {
  id: number;
  name: string;
  email: string;
};

const user: User = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

// getValueを使う
const userName = getValue(user, "name");
const userEmail = getValue(user, "email");
const userId = getValue(user, "id");

console.log(userName);  // "Taro"
console.log(userEmail); // "[email protected]"
console.log(userId);    // 1
TypeScript

ここでの型推論はこうなっています。

  • T は User
  • K は "name" / "email" / "id" のいずれか
  • getValue(user, "name") の戻り値型は User["name"] → string
  • getValue(user, "id") の戻り値型は User["id"] → number

つまり、呼び出し側から見ると、

  • "name" を渡せば戻り値は string
  • "id" を渡せば戻り値は number

というふうに、キーに応じて戻り値の型が変わる関数になっています。

間違ったキーを渡すとどうなるか

getValue(user, "naem"); // コンパイルエラー:Userにnaemは存在しない
TypeScript

K extends keyof T のおかげで、

  • K は必ず "id" | "name" | "email" のどれか
  • "naem" はその集合に含まれない

ということが型レベルで分かるため、 コンパイル時にエラーになります。

これがまさに 型安全なプロパティアクセス です。

getValueを別の型でも試してみる

Product型で使う

type Product = {
  id: number;
  title: string;
  price: number;
};

const product: Product = {
  id: 10,
  title: "Book",
  price: 1200,
};

const title = getValue(product, "title"); // 戻り値型はstring
const price = getValue(product, "price"); // 戻り値型はnumber

console.log(title); // "Book"
console.log(price); // 1200
TypeScript

ここでも、

  • T は Product
  • K は "id" | "title" | "price" のどれか
  • getValue(product, "title") の戻り値型は Product["title"] → string

というふうに、 型に応じて戻り値の型が自動的に変わる汎用関数として働いています。

Day 25 練習テンプレート:keyof・typeof・getValueをまとめて試す

最後に、今日の内容をひとつのファイルにまとめた練習テンプレートを載せます。 このままコピーして、型やオブジェクトを増やしながら、 keyof・typeof・T[K] の感覚を育ててみてください。

// day25-keyof-typeof-getValue.ts
// Day 25:keyof・typeof・型安全なプロパティアクセス

// 1. ベースとなる型
type User = {
  id: number;
  name: string;
  email: string;
};

// 2. keyofの例
type UserKeys = keyof User; // "id" | "name" | "email"

function printUserKey(key: UserKeys) {
  console.log("Userのキー:", key);
}

// 3. typeofの例
const config = {
  apiUrl: "https://example.com",
  timeout: 5000,
  retry: 3,
};

type Config = typeof config;        // configの型
type ConfigKeys = keyof typeof config; // "apiUrl" | "timeout" | "retry"

// 4. 型安全なプロパティアクセス関数
function getValue<T, K extends keyof T>(
  object: T,
  key: K
): T[K] {
  return object[key];
}

// 5. 使用例:User
const user: User = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

const userName = getValue(user, "name");  // string
const userEmail = getValue(user, "email"); // string
const userId = getValue(user, "id");       // number

console.log("userName:", userName);
console.log("userEmail:", userEmail);
console.log("userId:", userId);

// 間違ったキーはコンパイルエラー
// getValue(user, "naem"); // エラー:存在しないキー

// 6. 使用例:Product
type Product = {
  id: number;
  title: string;
  price: number;
};

const product: Product = {
  id: 10,
  title: "Book",
  price: 1200,
};

const title = getValue(product, "title"); // string
const price = getValue(product, "price"); // number

console.log("title:", title);
console.log("price:", price);
TypeScript

Day 25のまとめ

Day 25では、

  • keyof で「型からプロパティ名の集合を取り出す」考え方
  • typeof で「値からその型を取り出す」テクニック
  • keyof typeof で「実際のオブジェクトからキーの集合を型として取り出す」流れ
  • T, K extends keyof T, T[K] を使った 型安全なプロパティアクセス関数 getValue の仕組み

を、具体的なコードとともにじっくり見てきました。

最初は少し抽象的に感じるかもしれませんが、 getValue のような関数を自分で書けるようになると、

  • 「キーのスペルミス」
  • 「存在しないプロパティへのアクセス」

といったバグを、コンパイル時にぐっと減らせるようになります。

ぜひ、今日の getValue をベースに、

  • setValue<T, K extends keyof T>(object: T, key: K, value: T[K])
  • pluck<T, K extends keyof T>(array: T[], key: K): T[K][]

のような関数も試してみてください。 型と仲良くなりながら、コードの安心感もどんどん高まっていきます。


Day 25:Objectから「安全に値を取得する関数」を作る

Day 25の演習テーマは、とても実務で役立つ内容です。

Objectから安全に値を取得する関数を作る。

ここでの「安全」とは、

  • 存在しないプロパティ名を指定したらコンパイルエラーになる
  • キーに応じて戻り値の型が正しく変わる

という、型レベルで守られた安全性のことです。

キーワードは次の4つです。

  • keyof
  • typeof
  • keyof typeof
  • 型安全なプロパティアクセス

これらを組み合わせて、「安心してプロパティを読むための関数」をステップバイステップで作っていきます。

keyofで「そのObjectが持つキーの集合」を型にする

まずはkeyofの感覚をつかむ

keyof は、

「型から、そのプロパティ名の集合を取り出す演算子」

でした。

シンプルな例から確認してみます。

type User = {
  id: number;
  name: string;
  email: string;
};

type UserKeys = keyof User;
TypeScript

UserKeys は次のような型になります。

// 展開イメージ
type UserKeys = "id" | "name" | "email";
TypeScript

ここが重要なポイントです:

  • keyof User は「User型のプロパティ名だけが入っているユニオン型」です。
  • つまり、「Userに存在するキーだけ」を表す型になります。

この「キーの集合」を使うことで、 存在しないキーをコンパイル時に弾くことができるようになります。

typeofで「値から型を取り出す」

typeofの型的な使い方

TypeScriptでは、typeof を型の世界で使うことができます。

const config = {
  apiUrl: "https://example.com",
  timeout: 5000,
  retry: 3,
};

type Config = typeof config;
TypeScript

Config は次のような型になります。

// 展開イメージ
type Config = {
  apiUrl: string;
  timeout: number;
  retry: number;
};
TypeScript

ポイント:

  • typeof config は、「configという変数の型」をそのまま取り出します。
  • 実際のオブジェクトから型を作れるので、 「定数を元にした設定型」などを簡単に定義できます。

keyof typeofで「実際のObjectからキーの集合を型にする」

ここまでの2つを組み合わせると、 keyof typeof という形が出てきます。

const config = {
  apiUrl: "https://example.com",
  timeout: 5000,
  retry: 3,
};

type ConfigKeys = keyof typeof config;
TypeScript

ConfigKeys は次のような型になります。

// 展開イメージ
type ConfigKeys = "apiUrl" | "timeout" | "retry";
TypeScript

流れを整理すると:

  1. typeof config で「configの型」を取り出す
  2. keyof (typeof config) で「その型のプロパティ名の集合」を取り出す

つまり、

「実際のオブジェクトから、そのキーの集合を型として取り出す」

ことができるわけです。

型安全なプロパティアクセスのゴールイメージ

目指したいのは、こんな関数です。

function getValue<T, K extends keyof T>(
  object: T,
  key: K
): T[K] {
  return object[key];
}
TypeScript

この関数のゴールイメージは、

  • object に存在するキーしか key に渡せない
  • key に応じて戻り値の型が変わる

というものです。

たとえば User 型で使うとこうなります。

type User = {
  id: number;
  name: string;
  email: string;
};

const user: User = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

const name = getValue(user, "name"); // 戻り値型はstring
const id = getValue(user, "id");     // 戻り値型はnumber

// getValue(user, "naem"); // コンパイルエラー:存在しないキー
TypeScript

この「キーに応じて戻り値の型が変わる」感覚を、 これから少しずつ分解していきます。

Step 1:TとKの役割を理解する

まずは、ジェネリクスの型パラメータから見ていきます。

function getValue<T, K extends keyof T>(
  object: T,
  key: K
): T[K] {
  return object[key];
}
TypeScript

T:オブジェクト全体の型

  • T は「オブジェクト全体の型」を表します。
  • たとえば User や Product や Config などが入ります。

K extends keyof T:Tのキーだけを許可する

  • keyof T は「Tのプロパティ名の集合」でした。
  • K extends keyof T は、「Kはその集合の一員ですよ」という制約です。

つまり、

「KはTに存在するキーのどれかでなければならない」

というルールを型で表現しています。

これにより、存在しないキーを渡すとコンパイルエラーになります。

Step 2:引数と戻り値の型を読み解く

引数の型

object: T,
key: K
TypeScript
  • object は「T型のオブジェクト」
  • key は「Tに存在するプロパティ名のどれか(K)」

という意味です。

戻り値の型 T[K]

ここが少し難しいですが、とても重要なポイントです。

T[K] は、

「T型のオブジェクトの、キーKに対応するプロパティの型」

という意味になります。

たとえば、次のような型を考えます。

type User = {
  id: number;
  name: string;
  email: string;
};
TypeScript

このとき、

  • User["id"] は number
  • User["name"] は string
  • User["email"] は string

というように、「インデックスアクセス型」として扱えます。

T[K] は、このインデックスアクセス型をジェネリクスで一般化したものです。

Step 3:Objectから安全に値を取得する関数を実際に使ってみる

Userで試す

type User = {
  id: number;
  name: string;
  email: string;
};

const user: User = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

function getValue<T, K extends keyof T>(
  object: T,
  key: K
): T[K] {
  return object[key];
}

const userName = getValue(user, "name");  // 戻り値型はstring
const userEmail = getValue(user, "email"); // 戻り値型はstring
const userId = getValue(user, "id");       // 戻り値型はnumber

console.log("userName:", userName);
console.log("userEmail:", userEmail);
console.log("userId:", userId);

// 間違ったキーはコンパイルエラー
// getValue(user, "naem"); // エラー:Userにnaemは存在しない
TypeScript

ここで起きていること:

  • getValue(user, "name") のとき
    • T は User
    • K は "name"
    • 戻り値型は User["name"] → string
  • getValue(user, "id") のとき
    • T は User
    • K は "id"
    • 戻り値型は User["id"] → number

つまり、キーに応じて戻り値の型が変わる関数になっています。

Configで試す(keyof typeofの組み合わせ)

const config = {
  apiUrl: "https://example.com",
  timeout: 5000,
  retry: 3,
};

type Config = typeof config;
type ConfigKeys = keyof typeof config; // "apiUrl" | "timeout" | "retry"

const apiUrl = getValue(config, "apiUrl"); // string
const timeout = getValue(config, "timeout"); // number
const retry = getValue(config, "retry"); // number

console.log("apiUrl:", apiUrl);
console.log("timeout:", timeout);
console.log("retry:", retry);

// getValue(config, "unknown"); // コンパイルエラー:存在しないキー
TypeScript

ここでは、

  • typeof config で「configの型」を取り出し
  • keyof typeof config で「configのキーの集合」を取り出し
  • getValue で「そのキーに応じた型の値」を安全に取得しています。

Step 4:少し応用して「安全な設定アクセス関数」を作る

演習として、getValue をラップした「設定専用の関数」を作ってみます。

const appConfig = {
  appName: "MyApp",
  version: "1.0.0",
  debug: true,
  maxUsers: 100,
};

type AppConfig = typeof appConfig;
type AppConfigKey = keyof typeof appConfig;

// 設定専用の安全なアクセス関数
function getConfigValue<K extends AppConfigKey>(
  key: K
): AppConfig[K] {
  return appConfig[key];
}

const appName = getConfigValue("appName"); // string
const debug = getConfigValue("debug");     // boolean
const maxUsers = getConfigValue("maxUsers"); // number

console.log("appName:", appName);
console.log("debug:", debug);
console.log("maxUsers:", maxUsers);

// getConfigValue("unknown"); // コンパイルエラー:存在しないキー
TypeScript

ポイント:

  • getConfigValue は、appConfig 専用の「型安全なプロパティアクセス関数」です。
  • keyof typeof appConfig を使うことで、 「設定に存在しないキー」をコンパイル時に弾けます。
  • 戻り値の型も、キーに応じて string / boolean / number と変わります。

Day 25 練習テンプレート:Objectから安全に値を取得する関数

最後に、今日の内容をひとつのファイルにまとめた練習テンプレートを載せます。 このままコピーして、型やオブジェクトを増やしながら、 keyof・typeof・keyof typeof・T[K] の感覚を育ててみてください。

// day25-safe-object-access.ts
// Day 25:Objectから安全に値を取得する関数を作る

// 1. ベースとなる型
type User = {
  id: number;
  name: string;
  email: string;
};

// 2. keyofの例
type UserKeys = keyof User; // "id" | "name" | "email"

function printUserKey(key: UserKeys) {
  console.log("Userのキー:", key);
}

// 3. typeof + keyof typeof の例
const config = {
  apiUrl: "https://example.com",
  timeout: 5000,
  retry: 3,
};

type Config = typeof config;
type ConfigKeys = keyof typeof config; // "apiUrl" | "timeout" | "retry"

// 4. 型安全なプロパティアクセス関数
function getValue<T, K extends keyof T>(
  object: T,
  key: K
): T[K] {
  return object[key];
}

// 5. 使用例:User
const user: User = {
  id: 1,
  name: "Taro",
  email: "[email protected]",
};

const userName = getValue(user, "name");  // string
const userEmail = getValue(user, "email"); // string
const userId = getValue(user, "id");       // number

console.log("userName:", userName);
console.log("userEmail:", userEmail);
console.log("userId:", userId);

// getValue(user, "naem"); // コンパイルエラー:存在しないキー

// 6. 使用例:config
const apiUrl = getValue(config, "apiUrl"); // string
const timeout = getValue(config, "timeout"); // number
const retry = getValue(config, "retry"); // number

console.log("apiUrl:", apiUrl);
console.log("timeout:", timeout);
console.log("retry:", retry);

// 7. 設定専用の安全なアクセス関数
const appConfig = {
  appName: "MyApp",
  version: "1.0.0",
  debug: true,
  maxUsers: 100,
};

type AppConfig = typeof appConfig;
type AppConfigKey = keyof typeof appConfig;

function getConfigValue<K extends AppConfigKey>(
  key: K
): AppConfig[K] {
  return appConfig[key];
}

const appName = getConfigValue("appName"); // string
const debug = getConfigValue("debug");     // boolean
const maxUsers = getConfigValue("maxUsers"); // number

console.log("appName:", appName);
console.log("debug:", debug);
console.log("maxUsers:", maxUsers);

// getConfigValue("unknown"); // コンパイルエラー
TypeScript

Day 25演習のまとめ

Day 25の演習では、

  • keyof で「型からキーの集合を取り出す」
  • typeof で「値から型を取り出す」
  • keyof typeof で「実際のObjectからキーの集合を型にする」
  • T, K extends keyof T, T[K] を組み合わせて Objectから安全に値を取得する汎用関数 getValue を作る

という流れを、具体的なコードとともに体験しました。

最初は少し抽象的に感じるかもしれませんが、 getValue や getConfigValue のような関数を自分で書けるようになると、

  • キーのスペルミス
  • 存在しないプロパティへのアクセス

といったバグを、コンパイル時にぐっと減らせるようになります。

ぜひ、今日のパターンをベースに、

  • setValue<T, K extends keyof T>(object: T, key: K, value: T[K])
  • pluck<T, K extends keyof T>(array: T[], key: K): T[K][]

のような関数も試してみてください。 型と仲良くなりながら、コードの安心感もどんどん高まっていきます。

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