Mapped Types
インデックス型では設定時はどのようなキーも自由に設定できてしまい、アクセス時は毎回undefinedかどうかの型チェックが必要です。入力の形式が決まっているのであればMapped Typesの使用を検討できます。
Mapped Typesは主にユニオン型と組み合わせて使います。ここにサポートする言語を定義します。
tstypeSystemSupportLanguage = "en" | "fr" | "it" | "es";
tstypeSystemSupportLanguage = "en" | "fr" | "it" | "es";
これをインデックス型と同じようにキーの制約として使用することができます。
tstypeButterfly = {[key inSystemSupportLanguage ]: string;};
tstypeButterfly = {[key inSystemSupportLanguage ]: string;};
このようにButterflyを定義するとシステムがサポートしない言語、ここではdeが設定、使用できなくなります。
tsconstbutterflies :Butterfly = {en : "Butterfly",fr : "Papillon",it : "Farfalla",es : "Mariposa",Object literal may only specify known properties, and 'de' does not exist in type 'Butterfly'.2353Object literal may only specify known properties, and 'de' does not exist in type 'Butterfly'.: "Schmetterling", de };
tsconstbutterflies :Butterfly = {en : "Butterfly",fr : "Papillon",it : "Farfalla",es : "Mariposa",Object literal may only specify known properties, and 'de' does not exist in type 'Butterfly'.2353Object literal may only specify known properties, and 'de' does not exist in type 'Butterfly'.: "Schmetterling", de };
Mapped Typesを使ったユーティリティ型の紹介とその実現方法
プロパティを読み取り専用にするreadonlyをそのオブジェクトのすべてのプロパティに適用するReadonly<T>というユーティリティ型があります。
📄️ Readonly<T>
全プロパティを読み取り専用にする
Readonly<T>もこの機能で実現されています。Readonly<T>は次のように実装されています。
tstypeReadonly <T > = {readonly [P in keyofT ]:T [P ];};
tstypeReadonly <T > = {readonly [P in keyofT ]:T [P ];};
keyof Tという見慣れない表現が登場しましたが、これはオブジェクトのキーをユニオン型に変更するものだと解釈してください。keyofの詳細は型演算子をご覧ください。
📄️ keyof型演算子
keyofはオブジェクトの型からプロパティ名を型として返す型演算子です。たとえば、nameプロパティを持つ型に対して、keyofを使うと文字列リテラル型の"name"が得られます。
mapping modifier
-を先頭につけ-readonlyとすることで、逆に読み取り専用となっているプロパティを変更可能にするMutable<T>を作ることもできます(これはユーティリティ型にはありません)。このときの-をmapping modifierと呼びます。
tstypeImmutableButterfly =Readonly <Butterfly >;typeMutableButterfly = {-readonly [key inSystemSupportLanguage ]: string;};constimmutableButterfly :ImmutableButterfly = {en : "Butterfly",fr : "Papillon",it : "Farfalla",es : "Mariposa",};Cannot assign to 'en' because it is a read-only property.2540Cannot assign to 'en' because it is a read-only property.immutableButterfly .= "Schmetterling"; en constmutableButterfly :MutableButterfly = {en : "Butterfly",fr : "Papillon",it : "Farfalla",es : "Mariposa",};mutableButterfly .en = "Schmetterling"; // OK
tstypeImmutableButterfly =Readonly <Butterfly >;typeMutableButterfly = {-readonly [key inSystemSupportLanguage ]: string;};constimmutableButterfly :ImmutableButterfly = {en : "Butterfly",fr : "Papillon",it : "Farfalla",es : "Mariposa",};Cannot assign to 'en' because it is a read-only property.2540Cannot assign to 'en' because it is a read-only property.immutableButterfly .= "Schmetterling"; en constmutableButterfly :MutableButterfly = {en : "Butterfly",fr : "Papillon",it : "Farfalla",es : "Mariposa",};mutableButterfly .en = "Schmetterling"; // OK
mapping modifier(-)は他にもオプション修飾子の前につけて-?とすることで、オプション修飾子を取り除くことができます。これを使うことで、Partial<T>の逆の効果を持つRequired<T>を実装することができます。
📄️ Partial<T>
全プロパティをオプショナルにする
📄️ Required<T>
全プロパティを必須にする
インデックスアクセスの注意点
{ [K in string]: ... }のようにキーにstringなど、リテラル型でない型を指定した場合は、インデックスアクセスに注意してください。存在しないキーにアクセスしても、キーが必ずあるかのようにあつかわれるためです。
次の例のように、{ [K in string]: number }型のdictオブジェクトには、aキーはあるのに対し、bキーはありません。しかし、dict.bはnumberとして推論されます。
tsconstdict : { [K in string]: number } = {a : 1 };dict .b ;
tsconstdict : { [K in string]: number } = {a : 1 };dict .b ;
実際のdict.bの値はundefinedになるので、もしもdict.bのメソッドを呼び出すと実行時エラーになります。
tsconstdict : { [K in string]: number } = {a : 1 };console .log (dict .b );dict .b .toFixed (); // 実行時エラーが発生する
tsconstdict : { [K in string]: number } = {a : 1 };console .log (dict .b );dict .b .toFixed (); // 実行時エラーが発生する
このような挙動は、型チェックで実行時エラーを減らしたいと考える開発者にとっては不都合です。
この問題に対処するため、TypeScriptにはコンパイラオプションnoUncheckedIndexedAccessが用意されています。これを有効にすると、インデックスアクセスの結果の型がT | undefinedになります。つまり、undefinedの可能性を考慮した型になるわけです。そのため、dict.bのメソッドを呼び出すコードはコンパイルエラーになり、型チェックの恩恵が得られます。
tsconstdict : { [K in string]: number } = {a : 1 };dict .b ;'dict.b' is possibly 'undefined'.18048'dict.b' is possibly 'undefined'.dict .b .toFixed ();
tsconstdict : { [K in string]: number } = {a : 1 };dict .b ;'dict.b' is possibly 'undefined'.18048'dict.b' is possibly 'undefined'.dict .b .toFixed ();
📄️ noUncheckedIndexedAccess
インデックス型のプロパティや配列要素を参照したときundefinedのチェックを必須にする
Mapped Typesには追加のプロパティが書けない
Mapped Typesは追加のプロパティが定義できません。ここは、インデックス型とは異なる点です。
tstypeKeyValuesAndName = {[K in string]: string;A mapped type may not declare properties or methods.7061A mapped type may not declare properties or methods.: string; // 追加のプロパティ name };
tstypeKeyValuesAndName = {[K in string]: string;A mapped type may not declare properties or methods.7061A mapped type may not declare properties or methods.: string; // 追加のプロパティ name };
追加のプロパティがある場合は、その部分をオブジェクトの型として定義し、Mapped Typesとインターセクション型を成す必要があります。
tstypeKeyValues = {[K in string]: string;};typeName = {name : string; // 追加のプロパティ};typeKeyValuesAndName =KeyValues &Name ;
tstypeKeyValues = {[K in string]: string;};typeName = {name : string; // 追加のプロパティ};typeKeyValuesAndName =KeyValues &Name ;
上の例は、ひとつの型にまとめることもできます。
tstypeKeyValuesAndName = {[K in string]: string;} & {name : string; // 追加のプロパティ};
tstypeKeyValuesAndName = {[K in string]: string;} & {name : string; // 追加のプロパティ};