Record<Keys, Type>
Record<Keys, Type>はプロパティのキーがKeysであり、プロパティの値がTypeであるオブジェクトの型を作るユーティリティ型です。
Record<Keys, Type>の型引数
Keys
オブジェクトのプロパティーキーを指定します。Keysに代入できる型は、string、number、symbolとそれぞれのリテラル型です。
Type
オブジェクトのプロパティの値の型を指定します。任意の型が代入できます。
Recordの使用例
キーがstringで値がnumberのインデックス型を定義する。
tstypeStringNumber =Record <string, number>;constvalue :StringNumber = {a : 1,b : 2,c : 3 };
tstypeStringNumber =Record <string, number>;constvalue :StringNumber = {a : 1,b : 2,c : 3 };
キーがfirstName、middleName、familyNameで、値が文字列になるオブジェクトの型を定義する。
tstypePerson =Record <"firstName" | "middleName" | "lastName", string>;constperson :Person = {firstName : "Robert",middleName : "Cecil",lastName : "Martin",};
tstypePerson =Record <"firstName" | "middleName" | "lastName", string>;constperson :Person = {firstName : "Robert",middleName : "Cecil",lastName : "Martin",};
インデックスアクセスの注意点
Record<string, ...>のようにキーにstringなど、リテラル型でない型を指定した場合は、インデックスアクセスに注意してください。存在しないキーにアクセスしても、キーが必ずあるかのようにあつかわれるためです。
次の例のように、Record<string, number>型のdictオブジェクトには、aキーはあるのに対し、bキーはありません。しかし、dict.bはnumberとして推論されます。
tsconstdict :Record <string, number> = {a : 1 };dict .b ;
tsconstdict :Record <string, number> = {a : 1 };dict .b ;
実際のdict.bの値はundefinedになるので、もしもdict.bのメソッドを呼び出すと実行時エラーになります。
tsconstdict :Record <string, number> = {a : 1 };console .log (dict .b );dict .b .toFixed (); // 実行時エラーが発生する
tsconstdict :Record <string, number> = {a : 1 };console .log (dict .b );dict .b .toFixed (); // 実行時エラーが発生する
このような挙動は、型チェックで実行時エラーを減らしたいと考える開発者にとっては不都合です。
この問題に対処するため、TypeScriptにはコンパイラオプションnoUncheckedIndexedAccessが用意されています。これを有効にすると、インデックスアクセスの結果の型がT | undefinedになります。つまり、undefinedの可能性を考慮した型になるわけです。そのため、dict.bのメソッドを呼び出すコードはコンパイルエラーになり、型チェックの恩恵が得られます。
tsconstdict :Record <string, number> = {a : 1 };dict .b ;'dict.b' is possibly 'undefined'.18048'dict.b' is possibly 'undefined'.dict .b .toFixed ();
tsconstdict :Record <string, number> = {a : 1 };dict .b ;'dict.b' is possibly 'undefined'.18048'dict.b' is possibly 'undefined'.dict .b .toFixed ();
📄️ noUncheckedIndexedAccess
インデックス型のプロパティや配列要素を参照したときundefinedのチェックを必須にする
一方、Recordのキーが"firstName" | "lastName"のようなリテラル型だけで構成される場合は、noUncheckedIndexedAccessの設定にかかわらず、この問題は発生しません。キーが限定されているため、存在しないキーへのアクセスはコンパイルエラーになるからです。
ts// noUncheckedIndexedAccessがfalseの場合typePerson =Record <"firstName" | "lastName", string>;constperson :Person = {firstName : "Robert",lastName : "Martin",};constfirstName =person .firstName ;Property 'b' does not exist on type 'Person'.2339Property 'b' does not exist on type 'Person'.person .; // 存在しないキーへのアクセス b
ts// noUncheckedIndexedAccessがfalseの場合typePerson =Record <"firstName" | "lastName", string>;constperson :Person = {firstName : "Robert",lastName : "Martin",};constfirstName =person .firstName ;Property 'b' does not exist on type 'Person'.2339Property 'b' does not exist on type 'Person'.person .; // 存在しないキーへのアクセス b
キーがstringのときはnoUncheckedIndexedAccessを有効にすると、コンパイラーはundefinedを含めるようになりますが、キーがリテラル型(またはリテラル型のユニオン)のときは、コンパイラーはundefinedを含めないようになります。キーが必ずあることが、リテラル型によるキー指定によって自明だからです。
ts// noUncheckedIndexedAccessがtrueの場合typePerson =Record <"firstName" | "lastName", string>;constperson :Person = {firstName : "Robert",lastName : "Martin",};constfirstName =person .firstName ; // undefinedは含まれない
ts// noUncheckedIndexedAccessがtrueの場合typePerson =Record <"firstName" | "lastName", string>;constperson :Person = {firstName : "Robert",lastName : "Martin",};constfirstName =person .firstName ; // undefinedは含まれない
関連情報
📄️ インデックス型
TypeScriptで、オブジェクトのフィールド名をあえて指定せず、プロパティのみを指定したい場合があります。そのときに使えるのがこのインデックス型(index signature)です。たとえば、プロパティがすべてnumber型であるオブジェクトは次のように型注釈します。
📄️ Mapped Types
インデックス型では設定時はどのようなキーも自由に設定できてしまい、アクセス時は毎回undefinedかどうかの型チェックが必要です。入力の形式が決まっているのであればMapped Typesの使用を検討できます。
📄️ Map<K, V>
MapはJavaScriptの組み込みAPIのひとつで、キーと値のペアを取り扱うためのオブジェクトです。Mapにはひとつのキーについてはひとつの値のみを格納できます。