TypeScriptの型システムを理解するうえで、避けて通れないのがkeyofやtypeofといった演算子です。
でも、初めて見ると…
- これって何をしてるの?
- どういう場面で使うの?
- JavaScriptのtypeofとは何が違うの?
といった疑問が浮かんでくるかもしれません。
この記事では、TypeScriptでよく使われるkeyofとtypeofについて、以下の内容をサンプルコードを用いてわかりやすく解説します。
- keyofとは- オプショナル(?)なプロパティもkeyofで列挙される
- keyofは- interfaceにも使える
 
- オプショナル(?)なプロパティも
- typeofとは- JavaScriptとTypeScriptでのtypeofの違い
- as const(- constアサーション)と- typeofの組み合わせ
 
- JavaScriptとTypeScriptでの
- keyof typeofでオブジェクトのキーから型を生成する
- typeof Type[keyof typeof Type]でオブジェクトの値から型を生成する
keyofとは
keyofは「オブジェクト型のプロパティ名(キー名)を列挙したユニオン型」を返す演算子です。keyofは「型」に対して使用するもので、「変数」や「定数」には使えないので注意しましょう。
以下にサンプルコードを示します。
type Person = {
  id: number;
  name: string;
  email: string;
};
type PersonKeys = keyof Person;
// 上は次と同じ意味になる
type PersonKeys = 'id' | 'name' | 'email';PersonKeysという型は「文字列"id"、"name"、"email"」のどれかしか許されない型になります。つまり、Person型のプロパティ名だけを集めたユニオン型が作られるのです。
keyofは関数の引数に制限をかける際によく使います。以下にサンプルコードを示します。
function getValue(obj: Person, key: keyof Person) {  // 第二引数にkeyofを使用している(PersonKeysでもOK)
  return obj[key];
}
const taro: Person = {
  id: 1,
  name: 'Taro',
  email: 'taro@example.com',
};
getValue(taro, 'id'); // OK → 1
getValue(taro, 'email'); // OK → 'taro@example.com'
getValue(taro, "age"); // エラー:'age' は 'Person' に存在しないキー上記のサンプルコードでは、オブジェクトから任意のキーの値を取得する関数getValueを作っており、keyof Personを使うことで、第二引数keyに指定できる値を「文字列"id"、"name"、"email"」のいずれかに限定させています。
存在しないキー名を指定するとコンパイル時にエラーになります。安全なプロパティアクセスが保証されるため、バグの予防につながります。
オプショナル(?)なプロパティもkeyofで列挙される
オプショナル(?)なプロパティもkeyofで列挙されます。以下にサンプルコードを示します。
type Person = {
  id: number;
  name: string;
  email?: string; // ← オプショナルプロパティ(省略可能)
};
type PersonKeys = keyof Person;
// 上は次と同じ意味になる
type PersonKeys = 'id' | 'name' | 'email';emailは省略可能ですが、keyofで列挙されるということに注意してください。
keyofはinterfaceにも使える
keyofはinterfaceに対しても同様に使えます。以下にサンプルコードを示します。
interface Person {
  id: number;
  name: string;
  age: number;
}
type PersonKeys = keyof Person;
// 上は次と同じ意味になる
type PersonKeys = 'id' | 'name' | 'email';このように、typeとinterfaceのどちらを使っていてもkeyofの動作は同じです。
typeofとは
typeofはJavaScriptにもありますが、TypeScriptでは変数や値の型を取得する演算子です。keyofは「型」に対して使用しましたが、typeofは「変数」や「定数」に対して使用します。
以下にサンプルコードを示します。
const person = {
  id: 1,
  name: 'Taro',
  email: 'taro@example.com',
};
type Person = typeof person;
// 上は次と同じ意味になる
type Person = {
  id: number;
  name: string;
  email: string;
};このようにtypeofを使用すると、値から型を自動的に作ることができます。型定義を重複して書かなくてよいので便利です。
JavaScriptとTypeScriptでのtypeofの違い
JavaScriptのtypeofは実行時に値の型(文字列)を返しますが、TypeScriptのtypeofは型定義の中で使い、変数や値の「型」を返します。以下に違いを示します。
| 言語 | 書き方(一例) | 役割 | 戻り値 | 
|---|---|---|---|
| JavaScript | typeof person | 値の「型」を調べる(実行時) | "string"や"number"などの型(文字列) | 
| TypeScript | type Person = typeof person | 値の「型」を取得する(型定義) | 型そのもの(例: number型) | 
以下にJavaScriptとTypeScriptでtypeofを使用したサンプルコードを示します。
JavaScriptの例
const person = {
  id: 1,
  name: 'Taro',
  email: 'taro@example.com',
};
console.log(typeof person); // 'object'TypeScriptの例
const person = {
  id: 1,
  name: 'Taro',
  email: 'taro@example.com',
};
type Person = typeof person;
// 上は次と同じ意味になる
type Person = {
  id: number;
  name: string;
  email: string;
};
// 以下は、JavaScriptのtypeofであり、実行時に型を判定します。
// personはオブジェクトなので 'object'という文字列が返されます。
console.log(typeof person); // 'object'as const(constアサーション)とtypeofの組み合わせ
TypeScriptでは、定数オブジェクトを使って型を定義するとき、値が自動的に一般的な型(stringやnumber)として推論されてしまいます。定数オブジェクトの文字列や数値をそのまま型に反映したい場合はas constと組み合わせます。
as constを使わない場合
const COLORS = {
  RED: '#ff0000',
  GREEN: '#00ff00',
  BLUE: '#0000ff',
};
type ColorMap = typeof COLORS;
// 上は次と同じ意味になる
type ColorMap = {
  RED: string;
  GREEN: string;
  BLUE: string;
};ColorMapの型を見ると、各値はすべてstring型として扱われていることが分かります。たとえ"#ff0000"という文字列を入れていても、それが固定されるわけではありません。
as constを使う場合
一方、as constを使うと以下のようになります。as constを使うと各値がリテラル型("#ff0000"など)として固定され、readonlyなプロパティになります。
const COLORS = {
  RED: '#ff0000',
  GREEN: '#00ff00',
  BLUE: '#0000ff',
} as const;
type ColorMap = typeof COLORS;
// 上は次と同じ意味になる
type ColorMap = {
  readonly RED: '#ff0000';
  readonly GREEN: '#00ff00';
  readonly BLUE: '#0000ff';
};つまり、as constを使うと各プロパティの値がstring型ではなく、リテラル型の文字列として固定することができます。
keyof typeofでオブジェクトのキーから型を生成する
keyofとtypeofを組み合わせると、「オブジェクトのプロパティ名(キー名)を列挙したユニオン型」を取得することができます。
なぜkeyof typeofを組み合わせるの?
keyofは「型」に対して使用するため、「変数」や「定数」には使用することができません。そのため、typeofで値から型を取得した後にkeyofで「取得した型のプロパティ名(キー名)を列挙したユニオン型」を取得する必要があります。
つまり、「変数」や「定数」からkeyofを使いたいときは、まずtypeofで型に変換する必要があるのです。
サンプルコードを以下に示します。
const COLORS = {
  RED: '#ff0000',
  GREEN: '#00ff00',
  BLUE: '#0000ff',
};
type ColorKeys = keyof typeof COLORS;
// 上は次と同じ意味になる
type ColorKeys = 'RED' | 'GREEN' | 'BLUE';
function getColorCode(color: ColorKeys): string {
  return COLORS[color];
}
getColorCode('RED'); // OK
getColorCode('BLACK'); // エラー! 'BLACK' は ColorKeysにはないColorKeysという型は「文字列"RED"、"GREEN"、"BLUE"」のどれかしか許されない型になります。
もう少し詳しく説明すると、まず定数COLORSをtypeofした時点では以下のオブジェクト型になっています。
const COLORS = {
  RED: '#ff0000',
  GREEN: '#00ff00',
  BLUE: '#0000ff',
};
type ColorMap = typeof COLORS;
// 上は次と同じ意味になる
type ColorMap = {
  RED: string;
  GREEN: string;
  BLUE: string;
};このオブジェクト型ColorMapに対して、keyofを使用すると以下のようになります。
type ColorMap = {
  RED: string;
  GREEN: string;
  BLUE: string;
};
type ColorKeys = keyof ColorMap;
// 上は次と同じ意味になる
type ColorKeys = 'RED' | 'GREEN' | 'BLUE';typeof Type[keyof typeof Type]でオブジェクトの値から型を生成する
先ほどはkeyof typeofを使って、オブジェクトの「キー名」から型を作る方法を紹介しました。今度は、オブジェクトの「値」から型を作る方法を説明します。
as constとtypeof COLORS[keyof typeof COLORS]を組み合わせることで、オブジェクトの値から型を作ることができます。サンプルコードを以下に示します。
const COLORS = {
  RED: '#ff0000',
  GREEN: '#00ff00',
  BLUE: '#0000ff',
} as const; //as const を使うことで、各値('#ff0000' など)がリテラル型として扱われるようになります。
type ColorCode = (typeof COLORS)[keyof typeof COLORS];
// 上は次と同じ意味になる
type ColorCode = '#ff0000' | '#00ff00' | '#0000ff';keyof typeof COLORSで「文字列"RED"、"GREEN"、"BLUE"」のどれかしか許されない型を取得しています。その後、typeof COLORS[keyof typeof COLORS]ですべてのキーに対応する「値の型」("#ff0000"など)をユニオン型として取得しています。
本記事のまとめ
この記事では、TypeScriptでよく使われるkeyofとtypeofついて、以下のポイントを解説しました。
- keyofはオブジェクト型のプロパティ名(キー名)を列挙したユニオン型として取得する
- typeofは変数や値から型を取得する
- keyof typeofで、オブジェクトのキー名をユニオン型として取得できる
- typeof Type[keyof typeof Type]で、オブジェクトの値をユニオン型として取得できる
- as constを使えば、リテラル型として扱うことができる
keyofやtypeofをうまく使えば、型の定義がより明確になり、安全で保守しやすいコードが書けるようになります。
お読み頂きありがとうございました。