インデックスアクセス型は、JavaScriptのプロパティアクセスと同じかっこの構文で、別の型のプロパティの型を調べます。Person["age"] は number で、Person["address"]["city"] は string です。
利点は、信頼できる情報源が1つになることです。Age は number の2つ目のコピーではありません。Person["age"] が string に変われば、Person["age"] と書いたすべての型も一緒に変わります。インデックスアクセス型はコンパイル時だけのもので、JavaScriptは何も出力しません。
ユニオン型のキーと T[keyof T]
インデックスにはキーのユニオン型も使えます。結果はプロパティの型のユニオンです:
Product[keyof Product] は「Product の任意のプロパティの値」を型のレベルで表す方法です。存在しないキーを求めるとコンパイルエラーになります: Product["price"] は error TS2339: Property 'price' does not exist on type 'Product' です。
省略可能なプロパティは undefined を含む
strictNullChecks(strict で有効)のもとでは、省略可能なプロパティの型は undefined を含み、そのインデックスアクセス型も同じです:
type Profile = { name: string; nickname?: string };
type Nick = Profile["nickname"]; // string | undefined
undefined を取り除くには、囲みます: NonNullable<Profile["nickname"]> は string です。
T[number] で配列の要素の型を得る
配列は数値でインデックスアクセスするので、配列の型に number でインデックスアクセスすると要素の型が得られます。何段階にも重ねられます:
これは、生成されたAPIクライアントのように、自分が管理していない場所から型が来る場合に便利です。ApiResponse["data"]["users"][number] と書けば、誰も export していないユーザーの型に名前を付けられます。
タプル: 位置でインデックスアクセスする
タプル型は、特定の位置でインデックスアクセスでき、number ならすべての要素の型のユニオン、"length" ならリテラル型としての長さが得られます:
(typeof arr)[number]: const の配列からユニオン型を作る
現実でいちばんよくある使い方です。許可する値の一覧を配列として持ち(実行時にループできるように)、そこからユニオン型を作ります(コンパイラーがチェックできるように)。
2つの部分がこれを可能にしています。as const が配列をリテラル型の readonly タプルにします。これがないと ROLES は string[] で、(typeof ROLES)[number] はただの string です。そして typeof が値を型に変え、[number] がその要素をユニオン型にまとめます。かっこは省略でき(typeof ROLES[number] も同じ意味です)ますが、あると演算の順序がはっきりします。
文字列を配列と照合する
その readonly タプルには意外な点が1つあります。includes メソッドは要素の型しか受け付けないので、任意の文字列を渡せません:
index.ts(6,25): error TS2345: Argument of type 'string' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
チェックのために配列を readonly string[] に広げ、チェックが成功したら値が絞り込まれるように関数を 型ガード にしましょう:
このパターンなら、1つの一覧で両方の役割を果たせます。実行時のチェックは配列を使い、型はそこから作られるので、両者がずれることはありません。
ジェネリクスの中のインデックスアクセス: T[K]
ジェネリクスの中では、K extends keyof T と組み合わせた T[K] は、呼び出し側が選んだプロパティの型です:
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 28 },
];
const names = pluck(users, "name"); // string[]
const ages = pluck(users, "age"); // number[]
T[K] が正しい書き方になるのは K extends keyof T という制約があるからです。これがないと、K が T のキーであることをTypeScriptは知りようがなく、error TS2536: Type 'K' cannot be used to index type 'T' を報告します。
構文の一覧
| 書き方 | 意味 | 結果の例 |
|---|---|---|
T["key"] | 1つのプロパティの型 | Person["age"] は number |
T["a" | "b"] | それらのプロパティの型のユニオン | string | number |
T[keyof T] | すべてのプロパティの型のユニオン | すべての値の型 |
T["a"]["b"] | ネストしたプロパティ | Person["address"]["city"] |
Arr[number] | 配列の要素の型 | User[] なら User |
Tup[0] | その位置のタプルの要素 | 最初の要素の型 |
Tup["length"] | リテラル型としてのタプルの長さ | 3 |
(typeof arr)[number] | const の配列の値のユニオン | "admin" | "editor" |
T[typeof key] | 定数の型でインデックスアクセス | T["name"] と同じ |
インデックスは常に型です。key が const の変数である Person[key] は、2つのエラーで失敗します: error TS2538: Type 'key' cannot be used as an index type. と error TS2749: 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?。
よくある質問
TypeScriptのインデックスアクセス型とは何ですか?
T[K] と書く型で、型 T のプロパティ K の型を調べます。type Person = { name: string; age: number } なら、Person["age"] は number です。JavaScriptのプロパティアクセスと同じかっこの構文を使いますが、コンパイル時に型に対して働きます。
TypeScriptで配列の要素の型を取得するには?
配列の型に number でインデックスアクセスします。type Users = User[] なら、Users[number] は User です。配列の値に対しては、typeof と組み合わせます: (typeof users)[number]。
文字列の配列をユニオン型に変えるには?
TypeScriptがリテラルの値を保つように配列を as const で宣言し、number でインデックスアクセスします: const roles = ["admin", "editor"] as const; type Role = (typeof roles)[number]; で "admin" | "editor" が得られます。as const がなければ、要素の型はただの string です。
インデックスアクセス型のインデックスに変数を使えないのはなぜですか?
インデックスは型でなければならないからです。const key = "name" に対する Person[key] は、key が値なので失敗します(エラー TS2538 と TS2749)。Person[typeof key] と書くか、リテラルを直接使います: Person["name"]。
T[keyof T] はどういう意味ですか?
T をそのすべてのキーのユニオン型でインデックスアクセスするので、結果はすべてのプロパティの型のユニオンになります。{ a: string; b: number } なら string | number です。「このオブジェクトの任意の値」を型のレベルで表す方法です。