ユニオン分配 (union distribution)
ユニオン分配はジェネリクスで使われる型変数T
に対しユニオン型が指定された場合、その各要素に対してジェネリクスの型変数を適用することを指します。
たとえば、次のような型エイリアスがあります。
ts
typeWrapper <T > = {value :T ;};
ts
typeWrapper <T > = {value :T ;};
単なる値をオブジェクト包んだだけですが、このT
にユニオン型を代入してみると次のようになります。
ts
typeIntOrStr =Wrapper <number | string>;
ts
typeIntOrStr =Wrapper <number | string>;
予想通りの結果が出ますが、実はこれはユニオン分配によってWrapper<number> | Wrapper<string>
として評価されたあとの結果を示しています。
ただ、この例だけだとWrapper<number | string> = Wrapper<number> | Wrapper<string>
なのでいまいち理解が難しいかもしれませんが、Conditional Typesと併せて使うことによって、より複雑な型を作ることができます。
Distributive Conditional Types
ts
typeIsString <T > =T extends string ? true : false;
ts
typeIsString <T > =T extends string ? true : false;
この型を使ってstring
とnumber
のユニオン型を持つ変数T
に対してIsString
を適用すると、次のようになります。
ts
typeA =IsString <string>;typeB =IsString <number>;typeC =IsString <string | number>;
ts
typeA =IsString <string>;typeB =IsString <number>;typeC =IsString <string | number>;
IsString<T>
にstring
型を代入した型エイリアスA
はtrue
となります。一方string
型以外の型を代入した型エイリアスB
はfalse
となります。
そしてstring | number
型を代入した型エイリアスC
はstring | number
型としていっぺんに評価するのではなく、string
型とnumber
型が個別に評価されます。つまりC
はIsString<string> | IsString<number>
を評価していることと同じになり、結果としてtrue | false
が得られboolean
型となります。
もしstring | number
型がユニオン分配されずIsString
に適用されたとするとstring | number
型はstring
型の部分型ではないため、代入することができません。
ユニオン分配を起こさせない方法
ユニオン分配を意図的に起こさせない方法があります。方法は簡単で型変数を[]
で囲むだけです。
ts
typeNotDistribute <T > = [T ] extends [string] ? true : false;
ts
typeNotDistribute <T > = [T ] extends [string] ? true : false;
このNotDistribute
型はstring
型に対してはtrue
を返しますが、string | number
型に対してはfalse
を返します。string | number
型はstring
型の部分型ではないためです。
ts
typeA =NotDistribute <string>;typeB =NotDistribute <number>;typeC =NotDistribute <string | number>;
ts
typeA =NotDistribute <string>;typeB =NotDistribute <number>;typeC =NotDistribute <string | number>;