Record<Keys, Type>
Record<Keys, Type>
はプロパティのキーがKeys
であり、プロパティの値がType
であるオブジェクトの型を作るユーティリティ型です。
Record<Keys, Type>の型引数
Keys
オブジェクトのプロパティーキーを指定します。Keys
に代入できる型は、string
、number
、symbol
とそれぞれのリテラル型です。
Type
オブジェクトのプロパティの値の型を指定します。任意の型が代入できます。
Recordの使用例
キーがstring
で値がnumber
のインデックス型を定義する。
ts
typeStringNumber =Record <string, number>;constvalue :StringNumber = {a : 1,b : 2,c : 3 };
ts
typeStringNumber =Record <string, number>;constvalue :StringNumber = {a : 1,b : 2,c : 3 };
キーがfirstName
、middleName
、familyName
で、値が文字列になるオブジェクトの型を定義する。
ts
typePerson =Record <"firstName" | "middleName" | "lastName", string>;constperson :Person = {firstName : "Robert",middleName : "Cecil",lastName : "Martin",};
ts
typePerson =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
として推論されます。
ts
constdict :Record <string, number> = {a : 1 };dict .b ;
ts
constdict :Record <string, number> = {a : 1 };dict .b ;
実際のdict.b
の値はundefined
になるので、もしもdict.b
のメソッドを呼び出すと実行時エラーになります。
ts
constdict :Record <string, number> = {a : 1 };console .log (dict .b );dict .b .toFixed (); // 実行時エラーが発生する
ts
constdict :Record <string, number> = {a : 1 };console .log (dict .b );dict .b .toFixed (); // 実行時エラーが発生する
このような挙動は、型チェックで実行時エラーを減らしたいと考える開発者にとっては不都合です。
この問題に対処するため、TypeScriptにはコンパイラオプションnoUncheckedIndexedAccess
が用意されています。これを有効にすると、インデックスアクセスの結果の型がT | undefined
になります。つまり、undefined
の可能性を考慮した型になるわけです。そのため、dict.b
のメソッドを呼び出すコードはコンパイルエラーになり、型チェックの恩恵が得られます。
ts
constdict :Record <string, number> = {a : 1 };dict .b ;'dict.b' is possibly 'undefined'.18048'dict.b' is possibly 'undefined'.dict .b .toFixed ();
ts
constdict :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にはひとつのキーについてはひとつの値のみを格納できます。