ユーティリティ型で Distributive Conditional Type を理解する
なぜこの記事を書いたのか (本編は次の見出しから)
はむさん の 『ハンズオンで学ぶ TypeScript - JavaScript エンジニアのための TypeScript 徹底入門』 という Udemy の講座の中で Distributive Conditional Types が丁寧に解説されておりわかりやすかったので自分の理解度向上と復習を込めて記事を書きまた。
正直なところ、これを理解できたとて何の役に立つかはわかりません!
オリジナルな動的な型を作ることができるようになれるという感じでしょうか。(しらんけど。)
React × TypeScript についても触れているので、モダンフロントエンドを学びたい方にもオススです。
Conditional Types とは
型システムにロジックを持たせてることができる型です。条件分岐を使って動的な型を作ることができます。
JavaScript の三項演算子の記法で条件分岐を書くことができます。
type ConditionalTypes<T, U> = T extends U ? X : Y;
ちなみに、Conditional は英語で「条件付きの〜」という意味の形容詞です。
Distributive Conditional Types とは
T
にユニオン型が渡って来た時の Conditional Types です。
ここで言う「分配」はユニオン型の事を指していると理解して差し支えないでしょう。
type ConditionalTypes<T, U> = T extends U ? X : Y;
// T = string | number のようなユニオン型が渡ってくるのが Distributive Conditional Types
ちなみに、Distributive は英語「分配の〜」という意味の形容詞です。
Distributive Conditional Types はユーティリティ型に使われている
Distributive Conditional Types の仕組みを理解する前に、軽くユーティリティ型について説明しておきます。
ユーティリティ型とは、ある型から別の方を作成する型です。型引数に型を渡すことでその機能を実現させています。
型型言ってわけがわからないので、具体的に説明していきます。
いろんなユーティリティ型
- Exclude
- Extract
- Required
- Readonly
- Pick
- Omit
- etc…
ユーティリティ型 Exclude で Distributive Conditional Types を理解する
Exclude は、あるユニオン型から、特定の型を除外するユーティリティ型です。
ちなみに Exclude は英語で「除外する」という動詞です。
以下に 2 つの型を用意しました。
type JustFunctionType = () => void;
type MultipleTypes = string | number | JustFunctionType;
Exclude を使って、MultipleTypes
から文字列型のみ抽出したい場合は、以下のように書きます。
type JustStringType = Exclude<MultipleTypes, number | JustFunctionType>;
// 結果 type JustStringType = string;
使い方は、単純ですね。
なぜこのような除外が可能なのでしょうか?
TypeScipt の三項演算子?を理解する
Distributive Conditional Types について公式ドキュメントで以下のように説明されています。
For example, an instantiation of
T extends U ? X : Y
with the type argumentA | B | C
for T is resolved as(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
.
Distributive Conditional Types
日本語約すると以下の内容になります。(日本語訳と用語の使い方間違っていたらすいません!)
T extends U ? X : Y
のT
の型引数にA | B | C
が渡された場合T extends U ? X : Y
は(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
のように展開されます。
実際、Exclude は以下のコード実装されています。
type Exclude<T, U> = T extends U ? never : T;
三項演算子を使っているので条件によって返す値が違います。
T
がU
を~~継承できる~~互換性がある場合
neverが返り、そうでない場合は
T`が返ってくる条件になっています。
まずは、以下のJustStringType
を例に Exclude がどうゆう挙動をするか見ていきましょう。
type JustFunctionType = () => void;
type MultipleTypes = string | number | JustFunctionType;
type JustStringType = Exclude<MultipleTypes, number | JustFunctionType>;
まずは、公式ドキュメントの通り展開していきます。
type JustStringType =
(string extends number | JustFunctionType ? never : string)
| (number extends number | JustFnctionType ? never : number)
| (JustFunctionType extends number | JustFunctionType ? never : JustFunctionType);
はい。読みづらいですね。
一行ずつ処理して行きましょう。
まずはstring
から。
string
はnumber
もしくはJustFunctionType
を継承できない互換性がないので、string
が返ります。
string extends number | JustFunctionType ? never : string // 結果 string
次にnumber
を見てみましょう。
number
はnumber
もしくはJustFunctionType
を継承できない互換性がないので、never
が返ります。
number extends number | JustFunctionType ? never : number
// 結果 never
最後にJustFunctionType
を見てみます。
JustFunctionType
はnumber
もしくはJustFunctionType
を継承できない互換性がないので、never
が返ります。
JustFunctionType extends number | JustFunctionType ? never : JustFunctionType
// 結果 never
まとめると以下のようなります。
type JustStringType = Exclude<MultipleTypes, number | JustFunctionType>;
// 結果
type JustStringType = string | never | never;
結果number
もしくはJustFunctionType
が除外され、JustStringType = string
になるというわけです。
Thanks
Discussion