Conditional Types
Conditional Typesは日本語では条件付き型、型の条件分岐、条件型などと呼ばれ、ちょうど三項演算子のように?
と:
を使ってT extends U ? X : Y
のように書きます。これはT
がU
に割り当て可能である場合、X
になり、そうでない場合はY
になります。
このような場合だと型はtrue
になります。
ts
typeIsString <T > =T extends string ? true : false;consta :IsString <"a"> = true;
ts
typeIsString <T > =T extends string ? true : false;consta :IsString <"a"> = true;
たとえば、あるobject型のプロパティを読み取り専用にするReadonly<T>
というユーティリティ型があります。Readonly<T>
はそのオブジェクトの直下のプロパティを読み取り専用にしますが、ネストしたオブジェクトのプロパティは読み取り専用にしません。たとえば、次のようなオブジェクトがあるとします。
ts
typePerson = {name : string;age : number;address : {country : string;city : string;};};
ts
typePerson = {name : string;age : number;address : {country : string;city : string;};};
このときReadonly<Person>
ではaddress
プロパティ自体は読み取り専用になっており書き換えることはできませんが、address
のプロパティのcountry
とcity
は読み取り専用になっていません。上書きが可能です。
ts
constkimberley :Readonly <Person > = {name : "Kimberley",age : 24,address : {country : "Canada",city : "Vancouver",},};Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.kimberley .= "Kim"; name Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.kimberley .= 25; age Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.kimberley .= { address country : "United States",city : "Seattle",};kimberley .address .country = "United States";kimberley .address .city = "Seattle";
ts
constkimberley :Readonly <Person > = {name : "Kimberley",age : 24,address : {country : "Canada",city : "Vancouver",},};Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.kimberley .= "Kim"; name Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.kimberley .= 25; age Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.kimberley .= { address country : "United States",city : "Seattle",};kimberley .address .country = "United States";kimberley .address .city = "Seattle";
これを解決するにはReadonly<T>
を再帰的に適用する必要があります。このような場合にMapped TypesとConditional Typesを組み合わせて使います。
ts
typeFreeze <T > =Readonly <{[P in keyofT ]:T [P ] extends object ?Freeze <T [P ]> :T [P ];}>;
ts
typeFreeze <T > =Readonly <{[P in keyofT ]:T [P ] extends object ?Freeze <T [P ]> :T [P ];}>;
このようなFreeze<T>
を作ってみました。まずはこれを使ってみましょう。
ts
constkimberley :Freeze <Person > = {name : "Kimberley",age : 24,address : {country : "Canada",city : "Vancouver",},};Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.kimberley .= "Kim"; name Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.kimberley .= 25; age Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.kimberley .= { address country : "United States",city : "Seattle",};Cannot assign to 'country' because it is a read-only property.2540Cannot assign to 'country' because it is a read-only property.kimberley .address .= "United States"; country Cannot assign to 'city' because it is a read-only property.2540Cannot assign to 'city' because it is a read-only property.kimberley .address .= "Seattle"; city
ts
constkimberley :Freeze <Person > = {name : "Kimberley",age : 24,address : {country : "Canada",city : "Vancouver",},};Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.kimberley .= "Kim"; name Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.kimberley .= 25; age Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.kimberley .= { address country : "United States",city : "Seattle",};Cannot assign to 'country' because it is a read-only property.2540Cannot assign to 'country' because it is a read-only property.kimberley .address .= "United States"; country Cannot assign to 'city' because it is a read-only property.2540Cannot assign to 'city' because it is a read-only property.kimberley .address .= "Seattle"; city
Readonly<T>
とは異なり、address.country
とaddress.city
が書き換え不可能になりました。これはFreeze<T>
が再帰的に適用されているからです。
[P in keyof T]
の部分についてはMapped Typesのページで説明していますのでここでは簡潔に説明します。keyof T
はオブジェクトのキーをユニオン型に変更するものです。kimberley
の場合は"name" | "age" | "address"
になります。in
はその中のどれかを意味します。
T[P]
でオブジェクトのあるキーにおけるプロパティの型を取得します。その型がobject
であれば再起的にFreeze<T[P]>
を適用し、そうでなければT[P]
をそのまま使います。
📄️ Mapped Types
インデックス型では設定時はどのようなキーも自由に設定できてしまい、アクセス時は毎回undefinedかどうかの型チェックが必要です。入力の形式が決まっているのであればMapped Typesの使用を検討できます。
これによってオブジェクトを再帰的に凍結することができました。