Typescript(ルックアップ型)

はじめに

今回の記事はT[K]という形の構文で表すルックアップ型(Lookup Types)についての記事になります。オブジェクト型に対してプロパティ名でアクセスするようなものを型レベルにしたようなものです。
前回記事のkeyofも利用した記事となっているのでそちらも参考にして下さい。
Typescript(keyof演算子) - 駆け足エンジニアの記録

ルックアップ型の基本
interface Person {
  name: string;
  age: number;
}
type Typeofname = Person["name"]; //型はstring;
type Typeofage = Person["age"]; //型はnumber;

このようにしてオブジェクトのプロパティに対応するvalueの型にアクセスできます。

因みに、プロパティにオプションである?をつけると,ブジェクトのプロパティに対応するvalueの型とundifinedとのユニオン型で型を取得できます。

interface Person {
  name: string;
  age?: number;
}
type Typeofname = Person["name"]; //string;
type Typeofage = Person["age"]; //number|undifined;

多階層で構成されたオブジェクトについては以下のように型レベルでアクセスできます。

interface User {
  user1: { name: string; age: number };
  user2: { name: string; age: number };
}

type A = User["user1"];
//型は{name: string;age: number;}

type B = User["user1"]["name"];
//型はstring

type C = User["user1"]["age"];
//型はnumber

ルックアップ型の応用

①配列への利用

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

type Hoge = Array<string>;
type A = Hoge[number]; //型はstring

type Foo = string[];
type B = Foo[number]; //型はstring

type Bar = (string | boolean)[];
type C = Bar[number]; // 型はstring | boolean

配列のサブタイプであるタプル型については、キーとしてインデックスを指定して要素を取り出せます。

type Foo = [string, number];
type A = Foo[0]; //型はstring
type B = Foo[1]; //型はnumber
type C = Foo[number]; //型はstring | boolean
②keyofとの共存

keyofについて軽く復習しましょう。

interface Person {
  name: string;
  age: number;
}

type PropPerson = keyof Person;
//PropPersonの型は"name" | "age"

このようにkeyofには、オブジェクトの全てのプロパティを、文字列リテラル型のユニオン型(合併)で取得できるという機能があります。

これをルックアップ型と組み合わせると、

interface Person {
  name: string;
  age: number;
}
type A = Person[keyof Person];
//型はstring | number

このようにオブジェクトの各プロパティに対するvalueの型をユニオン型で取得できます。
またジェネリクスを用いて、型を関数のように扱うことが可能です。

interface Person {
  name: string;
  age: number;
}

type ValueOfType<T, U extends keyof T> = T[U];
type Typeofname = ValueOfType<Person, "name">;
//型はstring

最後にこの記事で扱った機能を全て活用すると、以下のようなオブジェクトから一部の値を取り出す関数を作り出すことができます。

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

const extractFromAPIResponse = <T, U extends keyof T>(
  obj: T,
  key: U
): T[keyof T] => {
  return obj[key];
};

const sampleData: APIResponse = {
  user: {
    id: 1,
    name: "Alice",
  },
  isPremiumUser: true,
};
console.log(extractFromAPIResponse(sampleData, "user"));
//{id: 1, name: "Alice"}

console.log(extractFromAPIResponse(sampleData, "isPremiumUser"));
//true