今日のお題
TypeScript 2.8に入る機能のお話
- https://github.com/Microsoft/TypeScript/pull/21316
- https://github.com/Microsoft/TypeScript/pull/21496
型のパターンマッチングができるようになる
1. Conditional Types
読んで字の如く、型定義における条件分岐
type MyCondition<T, U, X, Y> = T extends U ? X : Y;
「TがUに代入可能であればXを、そうでなければY」という型
誕生した背景
- 「型の差分を表現したい」: https://github.com/Microsoft/TypeScript/issues/12215
- 「再帰型を表現したい」: https://github.com/Microsoft/TypeScript/issues/12424
差分の表現方法
Conditional Typesは、Union Typesについて、分配律が成立
(T1 | T2) extends U ? X : Y = (T1 extends U ? X : Y) | (T2 extends U ? X : Y)
この性質とneverを組み合わせると、差分や絞込が簡単に表現できる
type Diff<T, U> = T extends U ? never : T;
type Result = Diff<("hoge" | "foo" | "piyo"), "foo">
// "hoge" | never | "piyo" = "hoge" | "piyo"
和型、ボトム型、条件型で差分型が表現できた、ということ
ちなみに keyof
, Mapped typesと組み合わせると...
type Diff<T, U> = T extends U ? never : T;
type $Diff<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] };
type Props = { name: string, age: number };
type DefaultProps = { age: number };
type RequiredProps = $Diff<Props, DefaultProps>;
declare function setProps<T extends RequiredProps>(props: T): void;
setProps({ name: "foo" });
setProps({ name: "foo", age: 42 }); // you can pass extra props too
setProps({ age: 42 }); // error, name is required
再帰型の終端指定
Mapped Typesによる再帰型で「終端」が表現可能に
type primitive = string | number | boolean | undefined | null;
type DeepReadonly<T> = T extends primitive ? T : DeepReadonlyObject<T>;
type DeepReadonlyObject<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
これだけでも割とすごい
2. Type Inference in Conditional Types
infer
でマッチした型をキャプチャできる
type ReturnType<T> = T extends ((...args: any[]) => infer R) ? R : never;
type ResolvedType<T> =
T extends Promise<infer R> ? R :
T extends Observable<infer R> ? R :
T;
所感
- おそらく、日常使いするものではなさそう
- 既存ライブラリの型定義を書くときに捗りそう
それでは楽しい型ライフを