2022年4月2日 りあクト! 第4章 TypeScriptで型をご安全に 4-5 条件付き型とテンプレートリテラル型(p.190~)
extends
クラスやインターフェースの拡張に使われるextendsキーワードは型引数の表現にも使うことが出来ます。
const override = <T, U extends T>(obj1: T, obj2: U): T & U => ({ ...obj1, ...obj2, }); override({a: 1}, {a: 2, b: 8}); // { a: 2, b: 8 } override({a: 2}, {x: 7}); // compile error!!
- (obj1: T, obj2: U)の仮引数に実引数を渡したオブジェクトが入ります。
- 型引数のTは第一引数のオブジェクトの型です。
- (ここがポイント)型引数の第二引数はUの型が第一引数Tの型を同じか拡張したものであることを表現しています。
- 関数内の処理でスプレッド構文を使用しており、同名のプロパティ名の値は上書きされています。(Reactでよく使う記法になるので覚えておくと良いでしょう)
条件付き型 (Conditional Types)
三項演算子を併用することで任意の条件による型を割り振ることが出来ます。 これを条件付き型と呼びます。
T extends U? X : Y
オブジェクトの型から任意のプロパティの型を抽出する例です。
type User = { id: unknown }; type NewUser = User & { id: string }; type OldUser = User & { id: number }; type Book = { isbn: string }; type IdOf<T> = T extends User ? T['id'] : never; type NewUserId = IdOf<NewUser>; //string type OldUserId=IdOf<OldUser>; //number type BookId = IdOf<Book>; // never
- &は交差型で『A かつ B』と複数の型をひとつに結合させます。
- type NewUserIdではIdOfの型引数TはNewUserが参照されます。
- NewUserの型がUserと同じ型か拡張した型の場合はNewUserが持つidの値の型を抽出して、そうでない場合はnever型を抽出します。
- T['id']の['id']はインデックスアクセス演算子です。
- BookIdはUserと同じ型か拡張した型ではないためnever型が抽出されます。
Userを消してもstringが抽出できる理由
ここでは先ほどのコード例からtype NewUserに定義されていたUser & {id: string}
という交差型であったUser
を消しても同様の結果が得られています。
type User = { id: unknown }; type NewUser = { id: string }; type IdOf<T> = T extends User ? T['id'] : never; type NewUserId = IdOf<NewUser>; // string
T extends User
はUser
と同じもしくは拡張した型という意味で三項演算子で条件分岐をしています。unknown
型は全ての型のスーパータイプであり、string
はサブタイプであるため、unknown
はstring
になりえるのでtrue
となり、T['id']
に処理が移ることでtype NewUserId
の結果値でstring
を得られています。
inferキーワード
条件付き型における型のマッチングでは、そのマッチング中に取得した型を出力にも利用できます。
以下は配列の要素の型を抽出するtype Flatten<T>
を定義しています。
type Flatten<T> = T extends Array<infer U> ? U : T; const num = 5; const arr = [3, 6, 9]; type A = Flatten<typeof arr>; //number type N = Flatten<typeof num>; // 5
- constで変数numを宣言したので、numの型は数値リテラルの5です。
type Flatten<T> = T extends Array<infer U> ? U : T; let num = 5; let arr = [3, "1", "1"]; type A = Flatten<typeof arr>; //number | string type N = Flatten<typeof num>; //number
Array<infer U>
と(infer U)[]
は同義です。Array<number>
number[]
と書くと配列であり中身はnumber
型を指定できていたのを、受け取った値で型推論するために使われるのがinfer
です。- 配列の中身を
number
とstring
にすると共用体型として型推論されます。