🔱

ユーティリティ型で Distributive Conditional Type を理解する

2022/02/13に公開

なぜこの記事を書いたのか (本編は次の見出しから)

はむさん『ハンズオンで学ぶ TypeScript - JavaScript エンジニアのための TypeScript 徹底入門』 という Udemy の講座の中で Distributive Conditional Types が丁寧に解説されておりわかりやすかったので自分の理解度向上と復習を込めて記事を書きまた。
正直なところ、これを理解できたとて何の役に立つかはわかりません!
オリジナルな動的な型を作ることができるようになれるという感じでしょうか。(しらんけど。)

https://www.udemy.com/course/ts-for-js-developers/

React × TypeScript についても触れているので、モダンフロントエンドを学びたい方にもオススです。

Conditional Types とは

型システムにロジックを持たせてることができる型です。条件分岐を使って動的な型を作ることができます。
JavaScript の三項演算子の記法で条件分岐を書くことができます。

ConditionalTypes
type ConditionalTypes<T, U> = T extends U ? X : Y;

ちなみに、Conditional は英語で「条件付きの〜」という意味の形容詞です。

Distributive Conditional Types とは

Tにユニオン型が渡って来た時の Conditional Types です。
ここで言う「分配」はユニオン型の事を指していると理解して差し支えないでしょう。

DistributiveConditionalTypes
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 つの型を用意しました。

example.ts
type JustFunctionType = () => void;
type MultipleTypes = string | number | JustFunctionType;

Exclude を使って、MultipleTypesから文字列型のみ抽出したい場合は、以下のように書きます。

example.ts
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 argument A | 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 : YTの型引数にA | B | Cが渡された場合T extends U ? X : Y(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)のように展開されます。

実際、Exclude は以下のコード実装されています。

Exclude
type Exclude<T, U> = T extends U ? never : T;

三項演算子を使っているので条件によって返す値が違います。
TU~~継承できる~~互換性がある場合neverが返り、そうでない場合はT`が返ってくる条件になっています。

まずは、以下のJustStringTypeを例に Exclude がどうゆう挙動をするか見ていきましょう。

example.ts
type JustFunctionType = () => void;
type MultipleTypes = string | number | JustFunctionType;
type JustStringType = Exclude<MultipleTypes, number | JustFunctionType>;

まずは、公式ドキュメントの通り展開していきます。

example.ts
type JustStringType =
  (string extends number | JustFunctionType ? never : string)
| (number extends number | JustFnctionType ? never : number)
| (JustFunctionType extends number | JustFunctionType ? never : JustFunctionType);

はい。読みづらいですね。
一行ずつ処理して行きましょう。

まずはstringから。
stringnumberもしくはJustFunctionType継承できない互換性がないので、stringが返ります。

string
string extends number | JustFunctionType ? never : string // 結果 string

次にnumberを見てみましょう。
numbernumberもしくはJustFunctionType継承できない互換性がないので、neverが返ります。

number
number extends number | JustFunctionType ? never : number
// 結果 never

最後にJustFunctionTypeを見てみます。
JustFunctionTypenumberもしくはJustFunctionType継承できない互換性がないので、neverが返ります。

JustFunctionType
JustFunctionType  extends number | JustFunctionType ? never : JustFunctionType
// 結果 never

まとめると以下のようなります。

example.ts
type JustStringType = Exclude<MultipleTypes, number | JustFunctionType>;
// 結果
type JustStringType = string | never | never;

結果numberもしくはJustFunctionTypeが除外され、JustStringType = stringになるというわけです。

Thanks

https://www.udemy.com/course/ts-for-js-developers/

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html

https://typescriptbook.jp/reference/type-reuse/utility-types

GitHubで編集を提案

Discussion