【TypeScript】typeof・keyof・Conditional Types・Mapped Typesを理解する
はじめに
typeof・keyof・Conditional Types・Mapped Typesについて、まとめていきたいと思います。
typeofとは
typeofは、変数や定数の値から型を取得するために使います。関数や変数の型を一括で取得でき、型定義の冗長さを減らせるという利点があります。
変数の型を取得し、それを新しい型として定義する例
const user = {
name: '太郎',
age: 30
};
// typeof user で、user オブジェクトの型を取得します
type UserType = typeof user;
// UserType は { name: string; age: number; } となります
const anotherUser: UserType = {
name: '花子',
age: 25
};
関数の戻り値の型を取得する例
これにより、関数の実装を変更しても型定義を自動的に更新できます。
// 関数を定義
function getUser() {
return {
id: 1,
name: "Alice",
active: true,
};
}
// 関数の戻り値型を取得
type User = ReturnType<typeof getUser>;
// User型の変数を定義
const user: User = {
id: 2,
name: "Bob",
active: false,
};
// エラーになる例(activeがbooleanじゃないのでコンパイルエラー)
const invalidUser: User = {
id: 3,
name: "Charlie",
active: "yes", // × Type 'string' is not assignable to type 'boolean'
};
keyofとは
keyofは オブジェクト型のプロパティ名を文字列リテラル型またはnumber型、symbol型のユニオンとして取り出す型演算子です。これにより、オブジェクトのキーに対して型安全にアクセスしたり、ユーティリティ型を作ることができます。
基本的な使い方
type User = {
id: number;
name: string;
active: boolean;
};
type UserKeys = keyof User;
// => "id" | "name" | "active"
const key: UserKeys = "name"; // OK
// const key2: UserKeys = "age"; // エラー
つまり keyof User は "id" | "name" | "active" というユニオン型になります。
Conditional Types(条件付き型)について
ある型が条件を満たすかどうかによって、異なる型を選択できる型システムの仕組みです。
三項演算子を型レベルで行うイメージです。
基本構文
型A extends 型B ? 型C : 型D
型A が 型B に代入可能(extends)であれば 型C を使う。そうでなければ 型D を使う
基本的な使い方
type IsString<T> = T extends string ? "Yes" : "No";
type A = IsString<string>; // "Yes"
type B = IsString<number>; // "No"
const testA: A = "Yes"; // OK
// const testAError: A = "No"; // ✕ エラー: 型 '"No"' を '"Yes"' に割り当てることはできません
const testB: B = "No"; // OK
// const testBError: B = "Yes"; // ✕ エラー
型の条件付き分岐
type TypeCheck<T> =
T extends number ? "number型です" :
T extends string ? "string型です" :
"その他の型です";
type X = TypeCheck<number>; // "number型です"
type Y = TypeCheck<string>; // "string型です"
type Z = TypeCheck<boolean>; // "その他の型です"
const checkX: X = "number型です"; // OK
const checkY: Y = "string型です"; // OK
const checkZ: Z = "その他の型です"; // OK
// 以下はエラー例
// const checkXError: X = "string型です"; // ✕ エラー
// const checkYError: Y = "その他の型です"; // ✕ エラー
inferの使い方
infer は 条件付き型の中で新しい型変数を「推論(infer)」するためのキーワード です。
主に「ある型から一部分を抽出したいとき」に使われます。基本構文は以下のようになります。
T extends U<infer X> ? X : Y
Conditional Types の「分配(distributive)」について
T extends U ? X : Y の T が「裸の型パラメータ」だと、T がユニオンの場合に各要素に分配される((A|B) extends ... が A extends ... | B extends ... のように評価される)。これを知らないと意図しない結果になります。分配を止めたい場合は タプルでラップ[T] extends [U] ? X : Y を使います。
// 分配される例
type F<T> = T extends string ? "S" : "O";
type R1 = F<"a" | 1>; // "S" | "O"
const r1a: R1 = "S"; // OK
const r1b: R1 = "O"; // OK
// NG: "X" は含まれていない
// const r1c: R1 = "X"; // エラー
// 分配を止める例
type G<T> = [T] extends [string] ? "S" : "O";
type R2 = G<"a" | 1>; // "O"
// OK: R2 は "O" のみ
const r2: R2 = "O";
// NG: "S" は割り当て不可
// const r2b: R2 = "S"; // エラー
Mapped Typesについて
Mapped Types は、既存の型(オブジェクト型など)のプロパティを変換・再構築するための型操作です。簡単に言うと 「型の各プロパティにルールを適用して新しい型を作る」 機能です。
基本構文
type 新しい型 = {
[Key in keyof 既存の型]: 変換後の型
};
プロパティに修飾子も付与可能な例
Mapped Types ではプロパティに readonly や ? などの修飾子を付けることができます。
type Person = {
name: string;
age: number;
};
// 全プロパティをオプショナルにする
type PartialPerson = {
[K in keyof Person]?: Person[K];
};
// 全プロパティを読み取り専用にする
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
// 修飾子を削除する例
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// --- 動作確認 ---
// PartialPerson の動作確認
const partialPerson1: PartialPerson = { name: "Alice" };
const partialPerson2: PartialPerson = { age: 30 };
const partialPerson3: PartialPerson = {};
console.log(partialPerson1);
console.log(partialPerson2);
console.log(partialPerson3);
// ReadonlyPerson の動作確認
const readonlyPerson: ReadonlyPerson = {
name: "Bob",
age: 25,
};
// 以下の行はコンパイルエラーになる
//readonlyPerson.name = "Charlie"; // Error: 'name' is a read-only property.
ポイント
-
?→ optional に -
readonly→ 読み取り専用に -
-?や-readonly→ 修飾子を解除
Conditional Typesとの組み合わせ
Mapped Types はConditional Typesと組み合わせて、型に応じた変換も可能です。
type Person = {
name: string;
age: number;
location: string;
};
// number 型だけを抽出する Mapped Type(不要なキーは消す)
type OnlyNumbers<T> = {
[K in keyof T as T[K] extends number ? K : never]: T[K];
};
type NumericPerson = OnlyNumbers<Person>;
/*
NumericPerson は以下と同じ
{
age: number;
}
*/
// --- 動作確認 ---
const example1: NumericPerson = {
age: 30, // number 型なのでOK
};
// const invalid1: NumericPerson = { name: "Alice" }; // プロパティ 'name' は存在しない
// const invalid2: NumericPerson = { location: "Tokyo" }; // プロパティ 'location' は存在しない
keyof と typeof を組み合わせるパターン
const COLORS = { RED: "red", GREEN: "green", BLUE: "blue" } as const;
// keyof typeof でキーのユニオン型を取得
type ColorKeys = keyof typeof COLORS; // "RED" | "GREEN" | "BLUE"
// 値のユニオン型を取得
type ColorValues = (typeof COLORS)[keyof typeof COLORS]; // "red" | "green" | "blue"
// Mapped Type と組み合わせてフラグ型を作る
type ColorFlags = {
[K in keyof typeof COLORS]?: boolean;
};
// 動作確認
const example2: ColorFlags = {
RED: true,
GREEN: false,
// BLUE は省略可能(?付きなのでOK)
};
// 値のユニオン型を使った関数
function setColor(color: ColorValues) {
console.log("Selected color:", color);
}
setColor("red"); // OK
// setColor("yellow"); // エラー: "yellow" は ColorValues に含まれない
代表的なユーティリティ型はMapped Typesで定義されている
| 型 | 役割 |
|---|---|
Partial<T> |
全プロパティを optional にする |
Required<T> |
全プロパティを必須にする |
Readonly<T> |
全プロパティを読み取り専用にする |
Record<K, T> |
指定したキー K のプロパティをすべて T 型にする |
Pick<T, K> |
特定のプロパティだけを抽出 |
Omit<T, K> |
特定のプロパティだけ除外 |
まとめ
-
typeofは、変数や関数の値から型を自動で取得できる -
keyofは、オブジェクト型のプロパティ名を文字列リテラル型、number型、symbol型のユニオンとして取得できる -
Conditional Typesは、ある型が条件を満たすかどうかによって、異なる型を選択できる -
Mapped Types既存の型の各プロパティにルールを適用して新しい型を作る
Discussion