30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

TypeScript のルックアップ型と keyof キーワード

TypeScript のルックアップ型を使うと、定義済みの型の一部分を取り出すことができる。
keyofキーワードと組み合わせることで、オブジェクトの各キーの型を取り出すこともできる。

この記事の内容は TypeScript のv3.9.5で動作確認している。

ルックアップ型の使い方

定義済みの型["キー"]と書くことで、指定したキーに対応する型を取り出すことができる。
これをルックアップ型という。

type Foo = {
  a: number;
  b: string;
  c?: boolean;
};

type X = Foo["a"]; // type X = number
type Y = Foo["b"]; // type Y = string
type Z = Foo["c"]; // type Z = boolean | undefined

存在しないキーにアクセスしようとすると、エラーになる。

type Q = Foo["d"]; // Property 'd' does not exist on type 'Foo'.ts(2339)

入れ子になっていても、問題なく使える。

type APIResponse = {
  user: {
    id: number;
    name: string;
    note?: string;
  };
};

type A = APIResponse["user"]["id"]; // type A = number
type B = APIResponse["user"]["name"]; // type B = string
type C = APIResponse["user"]["note"]; // type C = string | undefined

配列の要素は [number] で取り出せる

配列の場合、キーとして[number]を指定することで、要素を取り出せる。

type Foo = string[];

type A = Foo[number]; // type A = string

type Bar = (string | boolean)[];

type B = Bar[number]; // type B = string | boolean

タプルの場合は、numberではなくそのリテラル型を使うことで、インデックスを指定して要素を取り出せる。

type Foo = [boolean, string];

type A = Foo[number]; // type A = string | boolean
type B = Foo[0]; // type B = boolean
type C = Foo[1]; // type C = string

存在しないキーを指定するとエラーになる。

type D = Foo[2]; // Tuple type 'Foo' of length '2' has no element at index '2'.ts(2493)

ルックアップ型と keyof キーワードを組み合わせる

keyofキーワードは、オブジェクトのキーをstringの共用体型として取り出す機能。

type Foo = {
  a: number;
  b: string;
};

type A = keyof Foo; // type A = "a" | "b"

keyofはオブジェクトの各キーの名前を取り出す機能だが、これをルックアップ型と組み合わせることで、オブジェクトの各キーの(名前ではなく)型を取り出すことができる。

type Foo = {
  a: number;
  b: string;
};

type A = keyof Foo; // type A = "a" | "b"

type B = Foo[keyof Foo]; // type B = string | number

以下のextractFromAPIResponseはオブジェクトから一部の値を取り出す関数だが、このような関数の型も、ルックアップ型とkeyofキーワードで表現できる。

type User = {
  id: number;
  name: string;
  note?: string;
};

type APIResponse = {
  user: User;
  isPremiumUser: boolean;
};

const extractFromAPIResponse = (
  apiResponse: APIResponse,
  key: keyof APIResponse,
): APIResponse[keyof APIResponse] => {
  return apiResponse[key];
};

const sampleData: APIResponse = {
  user: {
    id: 1,
    name: "Alice",
  },
  isPremiumUser: true,
};

extractFromAPIResponse(sampleData, "user"); // boolean | User
extractFromAPIResponse(sampleData, "wrongKey"); // Argument of type '"wrongKey"' is not assignable to parameter of type '"user" | "isPremiumUser"'.ts(2345)

keyofStringsOnly フラグについて

keyof Tは、string | number | symbolのサブタイプである。

type Foo = keyof any; // type Foo = string | number | symbol

そのため、キーとしてnumbersymbolを使っていたとしても、それをそのまま取得できる。

const sym = Symbol();

type Bar = {
  a: unknown;
  1: unknown;
  [sym]: unknown;
};

type A = keyof Bar; // type A = typeof sym | "a" | 1

文字列以外のキーを無視したい場合は、tsconfig.jsonkeyofStringsOnlyを有効にする。

{
  "compilerOptions": {
    "keyofStringsOnly": true
  }
}
type Foo = keyof any; // type Foo = string

const sym = Symbol();

type Bar = {
  a: unknown;
  1: unknown;
  [sym]: unknown;
};

type A = keyof Bar; // type A = "a"