1. 条件型とは
以下、色の付いているサンプルコードは TypeScript: TS Playground で、コピペで動作します。変数 Result
の上をマウスでホバーすると型 true
が表示されてわかりやすいです。
//
// T が U の部分型であった場合 true を返す条件型 Conditional Type
//
type Extends<T, U> = T extends U ? true : false
//
// リテラル型はプリミティブ型の部分型
//
type Result0 = Extends<114514, number> // true
type Result1 = Extends<45450721, number> // true
type Result2 = Extends<'Kemono Friends', string> // true
type Result3 = Extends<'serial experiments lain', string> // true
type Result4 = Extends<true, boolean> // true
type Result5 = Extends<false, boolean> // true
2. union distirbutioin
Extends<0, 0 | 1>
は true
になります。しかし Extends<0 | 1, 0>
は boolean
になります。true
でもなければ false
でもありません、なぜでしょうか?
type Extends<T, U> = T extends U ? true : false
type Result0 = Extends<0, 0 | 1> // true
type Result1 = Extends<0 | 1, 0> // boolean
T extends U
の T
が Union 型だった場合、Union 型の要素1つ1つに分解して extends
を計算してしまうからです。
type Extends<T, U> = T extends U ? true : false
type Result1 = Extends<0 | 1, 0> // boolean
// type Result1 = ( Extends<0 | 1, 0> )
// type Result1 = ( 0 | 1 extends 0 ? true : false )
// type Result1 = ((0 extends 0 ? true : false) | ( 1 extends 0 ? true : false))
// type Result1 = (( true | false ))
// type Result1 = ( true | false )
// type Result1 = boolean
対策として以下のように配列 []
で括ると Union Distribution が起こりません。
type Extends<T, U> = T[] extends U[] ? true : false
type Result0 = Extends<0, 0 | 1> // true
type Result1 = Extends<0 | 1, 0> // false
また Union Distribution が起こるのはジェネリクスを使用したときのみで、ベタ書きすれば Union Distribution は起こりません。
type Result0 = 0 extends 0 | 1 ? true : false // true
type Result1 = 0 | 1 extends 0 ? true : false // false
◯ 御礼
uhyo さんの記事からこの動作をしりました。本当に本当にありがとうございます。リンクを貼ると先方の記事にリンクができてしまうのでタイトル名のみ掲載いたします。
- TypeScriptの型初級 - Qiita
- TypeScriptの型演習 - Qiita
- TypeScriptの型演習(解答・解説編) - Qiita
書籍も買わせていただきました。
◯ 追記
union distribution を避ける際には T[] より [T] が公式の見解らしいです。
TypeScriptのconditional typesで分配の発生を避ける方法はいろいろあるが、公式には [X] extends [Y] ? ... の形を推奨しているのか。
— 🈚️うひょ🤪✒📘 TypeScript本発売🫐 (@uhyo_) November 5, 2023
しかも「square bracketsで囲む」という説明の仕方でそうなのという気持ち(?)https://t.co/Sex1i4f3aI.
3. extends
◯ 機能
TypeScript では extends
と書かれていた場合、5 つの機能があります。
- class のための
extends
- interface のための
extends
- Generics を使った型の制約のための
extends
- Conditional Types を使った条件分岐のための
extends
- 4 における infer を使った推論のための
extends
2~5 の機能についてリンク先で解説されています。
◯ 意味
上記のように extends
には 5 つの機能がありますが、
T extends U
と書かれていた場合、 常に T
型のオブジェクトは、 U
型のオブジェクトが書かれているところでも使えることを表しています。
TypeScript ではここを含めていくつかの場面で S extends T という構文が登場しますが、これは常に「S は T の 部分型 である」という意味です。(P. 101 脚注)
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで
部分型っていつ使うんですか?どんなときに便利なんですか?って聞かれたら extends
のときに使います、extends
を使うときに便利ですと答えるのがよいのかなと思います。
4. 公式ドキュメント
いやいや、お前の「T
型のオブジェクトは、 U
型のオブジェクトが書かれているところでも使える」って表現はあってんのかよ?という感じがするのですが。以下は公式ドキュメントの条件型 Conditional Types の項目からの抜粋を見ると、正しくはないけど、妥当かなという気がします。
SomeType extends OtherType ? TrueType : FalseType;
extendsの左側の型が右側の型に代入可能な場合、 最初のブランチ(「true」ブランチ)の型が得られ、そうでない場合、後のブランチ(「false」ブランチ)の型が得られる。
When the type on the left of the extends is assignable to the one on the right, then you’ll get the type in the first branch (the “true” branch); otherwise you’ll get the type in the latter branch (the “false” branch).
TypeScript: Documentation - Conditional Types
条件型, Conditional Type は募集要項の1つに挙げられています。このことから条件型は TypeScript の学習のマイルストーンのひとつになるかなと思いました。
- 必須スキル
- 型関数や、conditional typeなどを利用したTypeScriptでの開発経験