1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Union Distribution とは

Union Distribution とは、Conditional Types と呼ばれる型の型引数(ジェネリック型)にユニオン型が渡された際に、そのユニオン型に対して分配(distribution)が行われるという性質のことです。

本記事では、Conditional Types の構文についてと、ユニオン型を渡した際にどのように型が得られるのかについて見ていきたいと思います。

Conditional Types について

Conditional Types とは、条件分岐によって表せる型のことで、T extends U ? A : Bという構文で表されます(Tは型引数になります)。
T extends U ? A : Bは、T型がU型の部分型であればA型を、そうでなければB型となる型を表します。
T extends Uの部分をboolean のように捉えると、三項演算子と同じ構造になっていますね。)

type IsString<T> = T extends string ? true : false

上の型は、Tstring型の部分型であればtrue型となり、そうでなければfalse型となります。

ユニオン型を渡すと分配が起こるDistributive Conditional Types

Conditional Types の型引数にユニオン型を渡す形については特別な名前がついており、Distributive Conditional Typesと呼ばれます。

以下で、型引数Tstring型の場合にTの配列型を返す型ToArray<T>を考えてみます。

type ToArray<T> = T extends string ? T[] : never

//type UnionArray = "octopus"[] | "squid"[]
type UnionArray = ToArray<"octopus"|"squid"> 

ToArray<T>型にstring型の部分型であるようなユニオン型"octopus"|"squid"を渡した際に得られる型を見てみます。

UnionArrayの型としては、構文を文字通りみていくと"(octopus"|"squid")[]という型になりそうですが、Union Distribution が働くため、実際にはユニオン型の構成要素が分配される形となり、"octopus"[] | "squid"[]型となります。

ちなみに"(octopus"|"squid")[]のようなユニオン型の配列型を得たい場合には、以下のコード例のようにextendsの前後を[]で囲むことでUnion Distribution が働かないようにできます。

type X<T> = [T] extends [string] ? T[] : never

type Y = X<"octopus" | "squid" > //type Y = ("octopus" | "squid")[]

もしくはTの部分を[T]とすることでも(煩雑になりますが)取得可能ではあります。

type X<T> = T extends string ? [T] : never
//type Y = ("octopus" | "squid")[]
type Y = X<"octopus" | "squid" >[number][]

ユーティリティ型で使われるDistributive Conditional Types

Distributive Conditional Types は少々難解なため、使用頻度はそこまで高くはないかもしれませんが、Extract型やExclude型といった組み込み型がどのように実装されているのを理解するのに役立ちます。

以下はExclude型とExtract型の実装部分で、Distributive Conditional Types が利用されています。

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

Extract の動きを追う

Extract型がどのように型を決定しているのかをみていきたいと思います。

Extract<T, U>型は、T型からU型の部分型を抽出するユーティリティ型です。
以下のように'octopus' | 'squid'型と'octopus' | 'tuna'型をExtractに渡してみると、'octopus'型が抽出されます。

type Octopus = Extract<'octopus' | 'squid', 'octopus' | 'tuna'>
//type Octopus = "octopus"

どのようにUnion Distribution が起きているかを、Extractの形を少し変えてみていきます。
Extract<'octopus' | 'squid', 'octopus' | 'tuna'>を、Exclude<'octopus' | 'squid', T>として考えてみます。
以下のコードではOctopusSquid<T>にあたります。

type OctopusSquid<T> = T extends 'octopus' | 'squid' ? T : never

type X = OctopusSquid<'octopus' | 'tuna'>
//type X = "octopus"

OctopusSquid<T>の型引数T'octopus' | 'tuna'型を渡すと、Union Distribution によって、各要素ごとに条件判定がなされる形となります。

まず、'octopus' | 'tuna''octopus'部分について、こちらは'octopus' | 'squid'の部分型となっているため、T extends 'octopus' | 'squid'の条件を満たしており、T型、つまり'octopus'型と判定されます。

次に、'octopus' | 'tuna''tuna'部分について、こちらは'octopus' | 'squid'の部分型ではないため、never型と判定されます。


最後に、この2つの結果のユニオン型をとることで、Extract<'octopus' | 'squid', 'octopus' | 'tuna'>が得られることになります。

得られる型は'octopus' | neverとなるはずですが、never型とのユニオンをとるとnever型は消えてしまいますので(「空集合との和集合」と捉えるとわかりやすいです)、最終的に得られる型は'octopus'型となるわけです。


以上のように少々難解な動きをするDistributive Conditional Types ですが、ユーティリティ型の実装にロジック部分が組み込まれることで、利用する敷居が下がっていてユーティリティ型となっている有用性を感じられますね。


1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?