6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

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 UserUser と同じもしくは拡張した型という意味で三項演算子で条件分岐をしています。
  • unknown 型は全ての型のスーパータイプであり、string はサブタイプであるため、unknownstring になりえるので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 です。
  • 配列の中身をnumberstring にすると共用体型として型推論されます。

参考

りあクト! 【Ⅰ. 言語・環境編】 p.190~191