- Day 25:keyof・typeofで「型安全なプロパティアクセス」を手に入れる
- keyofとは何か:型から「プロパティ名の集合」を取り出す
- typeofとは何か:値から「その型」を取り出す
- keyof typeofの組み合わせ:値から「キーの集合」を取り出す
- 型安全なプロパティアクセスとは何か
- いよいよ本題:getValue<T, K extends keyof T> を分解する
- getValueを具体的な型で試してみる
- getValueを別の型でも試してみる
- Day 25 練習テンプレート:keyof・typeof・getValueをまとめて試す
- Day 25のまとめ
- Day 25:Objectから「安全に値を取得する関数」を作る
- keyofで「そのObjectが持つキーの集合」を型にする
- typeofで「値から型を取り出す」
- keyof typeofで「実際のObjectからキーの集合を型にする」
- 型安全なプロパティアクセスのゴールイメージ
- Step 1:TとKの役割を理解する
- Step 2:引数と戻り値の型を読み解く
- Step 3:Objectから安全に値を取得する関数を実際に使ってみる
- Step 4:少し応用して「安全な設定アクセス関数」を作る
- Day 25 練習テンプレート:Objectから安全に値を取得する関数
- Day 25演習のまとめ
Day 25:keyof・typeofで「型安全なプロパティアクセス」を手に入れる
Day 25では、TypeScriptの中でも“ちょっと玄人っぽい”けれど、 使いこなせると一気にコードが洗練されるテクニックを扱います。
キーワードはこの4つです。
keyoftypeofkeyof 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流れを整理すると:
typeof configで「configの型」を取り出す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
TypeScriptobjectは「T型のオブジェクト」keyは「Tに存在するプロパティ名のどれか(K)」
という意味です。
戻り値の型 T[K] を理解する
ここが少し難しいですが、とても重要なポイントです。
T[K] は、
「T型のオブジェクトの、キーKに対応するプロパティの型」
という意味になります。
たとえば、次のような型を考えます。
type User = {
id: number;
name: string;
email: string;
};
TypeScriptこのとき、
User["id"]はnumberUser["name"]はstringUser["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はUserKは"name"/"email"/"id"のいずれかgetValue(user, "name")の戻り値型はUser["name"]→stringgetValue(user, "id")の戻り値型はUser["id"]→number
つまり、呼び出し側から見ると、
"name"を渡せば戻り値はstring"id"を渡せば戻り値はnumber
というふうに、キーに応じて戻り値の型が変わる関数になっています。
間違ったキーを渡すとどうなるか
getValue(user, "naem"); // コンパイルエラー:Userにnaemは存在しない
TypeScriptK 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はProductKは"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);
TypeScriptDay 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つです。
keyoftypeofkeyof typeof- 型安全なプロパティアクセス
これらを組み合わせて、「安心してプロパティを読むための関数」をステップバイステップで作っていきます。
keyofで「そのObjectが持つキーの集合」を型にする
まずはkeyofの感覚をつかむ
keyof は、
「型から、そのプロパティ名の集合を取り出す演算子」
でした。
シンプルな例から確認してみます。
type User = {
id: number;
name: string;
email: string;
};
type UserKeys = keyof User;
TypeScriptUserKeys は次のような型になります。
// 展開イメージ
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;
TypeScriptConfig は次のような型になります。
// 展開イメージ
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;
TypeScriptConfigKeys は次のような型になります。
// 展開イメージ
type ConfigKeys = "apiUrl" | "timeout" | "retry";
TypeScript流れを整理すると:
typeof configで「configの型」を取り出す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];
}
TypeScriptT:オブジェクト全体の型
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
TypeScriptobjectは「T型のオブジェクト」keyは「Tに存在するプロパティ名のどれか(K)」
という意味です。
戻り値の型 T[K]
ここが少し難しいですが、とても重要なポイントです。
T[K] は、
「T型のオブジェクトの、キーKに対応するプロパティの型」
という意味になります。
たとえば、次のような型を考えます。
type User = {
id: number;
name: string;
email: string;
};
TypeScriptこのとき、
User["id"]はnumberUser["name"]はstringUser["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はUserKは"name"- 戻り値型は
User["name"]→string
getValue(user, "id")のときTはUserKは"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"); // コンパイルエラー
TypeScriptDay 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][]
のような関数も試してみてください。 型と仲良くなりながら、コードの安心感もどんどん高まっていきます。
