😺

【TypeScript】typeof・keyof・Conditional Types・Mapped Typesを理解する

に公開

はじめに

typeofkeyofConditional TypesMapped 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