[[条件付き型 (TypeScript)|条件付き型]]で[[型パラメーター (TypeScript)|型パラメーター]]に[[ユニオン型 (TypeScript)|ユニオン型]]が[[型引数 (TypeScript)|型引数]]として指定されたとき、[[条件付き型 (TypeScript)|条件付き型]]を使わない[[型エイリアス (TypeScript)|型エイリアス]]とは結果が異なり、分配的(Distributive)になる。 具体的には以下2ステップで評価される。 1. [[ユニオン型 (TypeScript)|ユニオン型]]の1つ1つに対して型を算出する 2. 1の結果をすべてマージした[[ユニオン型 (TypeScript)|ユニオン型]]を結果とする ```ts // 普通にtypeキーワードを使うと分配的にならない type ThreeTuple<T> = [T, T, T]; type A = ThreeTuple<string | number>; // ^? type A = [string | number, string | number, string | number] // Conditionaly Typesを使うと分配的になる type CThreeTuple<T> = T extends any ? [T, T, T] : never; type B = CThreeTuple<string | number>; // ^? type B = [string, string, string] | [number, number, number] // Conditionaly Typesを使いつつ分配的を割けるなら[]で囲む type WorkaroundCThreeTuple<T> = [T] extends [any] ? [T, T, T] : never; type C = WorkaroundCThreeTuple<string | number>; // ^? type C = [string | number, string | number, string | number] ``` <button class="playground"><a href="https://www.typescriptlang.org/play?#code/PTAEjszQsBMawYBcE8AOBTQtQyB+GQ9wzsJMMgkhkH95QMQZALBkDAlQWUTAQt2kCsGQSIZbARBgCgEVQAVACwCdlknAK6IANsgA8nAHygAvKADanADRc1nALoBuNkmSgAgvK58BwsZIDOsXgEsAdgHNQAH1AOhAWwBGyXtK6IKAhAHoA-CwswQDCAPYOACZ2sHYJAIai8Fz6VoSklDS0gNEMehwxPPyCIuJSsgqcoMgAHrDISVag6Q7Z4Uqq6lyaoABcHsgAbv667AYAQiYVZtWWEjb2zm4e3n4BQWBhkdFg8UkpaQ6Z2Zy5hEyAJgz3hXiATkqAigzFDIqagOYMgE9qgACGMoGADqcV4AGt0rw4kIkksqhZajITMphi02h0lN14MM+soNBphmMHJNpsDQDETGDIdDYfDKuYatZbI4XO5PL5-IFjiFQBEWEA">Playground</a></button> ## Excludeによる具体的な説明 上記のサンプルコードだけではピンと来ないと思うので、より具体的な例で詳しく説明する。 `Exclude`で[[リテラル型 (TypeScript)|リテラル型]]の[[ユニオン型 (TypeScript)|ユニオン型]]から特定の[[リテラル型 (TypeScript)|リテラル型]]を除外するケースを考える。`Exclude`の実装は以下のようになっている。 ```ts type Exclude<T, U> = T extends U ? never : T; ``` ここで以下の`SafeColor`型を定義する。 ```ts type Color = "red" | "blue" | "green" | "yellow"; type DangerColor = "red" | "yellow"; type SafeColor = Exclude<Color, DangerColor>; // ^? type SafeColor = "blue" | "green" ``` <button class="playground"><a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAwg9gGzgJygXigImRAJpqAHywCMEBXCA4zAcxwgDtqsQIEkB3TAbgChQkKABEAho1oRk8JKgzY8LTGw5xu-QdADKogGYQZKdFACiADwDGFXBAA8h5ABoR4ydMQoAfPwD0PqAEAegD8fEA">Playground</a></button> `Exclude`の定義を当てはめてみる。 ```ts type Exclude<Color, DangerColor> = Color extends DangerColor ? never : Color; ``` 右辺を展開すると ```ts "red" | "blue" | "green" | "yellow" extends "red" | "yellow" ? never : "red" | "blue" | "green" | "yellow"; ``` ソースの通りに解釈してしまうと、『`"red" | "blue" | "green" | "yellow"`は`"red" | "yellow"`に代入できない』ので、この結果は `"red" | "blue" | "green" | "yellow"` となりそうである。だが実際は `"blue" | "green"` が期待される。 これは[[条件付き型は分配的 (TypeScript)|条件付き型は分配的]]なことから算出された結果であり、具体的には以下のような解釈をする。 1. [[ユニオン型 (TypeScript)|ユニオン型]]の1つ1つに対して型を算出する 2. 1の結果をすべてマージした[[ユニオン型 (TypeScript)|ユニオン型]]を結果とする ### 1について 以下の`extends`より手前を分解する。 ```ts "red" | "blue" | "green" | "yellow" extends "red" | "yellow" ? never : "red" | "blue" | "green" | "yellow"; ``` すると、4つの文ができる。 ```ts "red" extends "red" | "yellow" ? never : "red"; "blue" extends "red" | "yellow" ? never : "blue"; "green" extends "red" | "yellow" ? never : "green"; "yellow" extends "red" | "yellow" ? never : "yellow"; ``` それぞれの結果は以下のようになる。 ```ts never "blue" "green" never ``` ### 2について これらをマージした[[ユニオン型 (TypeScript)|ユニオン型]]を作る。 ```ts never | "blue" | "green" | never ``` [[TypeScriptのnever型をユニオン型の一部に指定しても結果は変わらない]]ため、これは `"blue" | "green"` となり、期待値と一致する。