直感に反しているExclude型についてconditional typeの話をしつつ解説します。
Exclude型とは?
Union型から特定の要素を取り除く型です。ある型から特定のプロパティを取り除きたいときに使えます。
interface Person { name: string; age: number; country: string; } type PersonWithoutAge = Pick<Person, Exclude<keyof Person, "age">>;
Exclude型の実装と解説
Exclude型は簡潔に実装できます。
type Exclude<T, K> = T extends K ? never : T;
わけがわからないと思うので1つずつ解説します。
extendsキーワードと三項演算子
extendsは「右辺は左辺のサブクラスであるか」を判定します。特にここではプロパティ名同士の比較になるので、単に等しいかどうかを判定しています。
そして、T extends Kが真であったならneverを、偽であったならばTを返します。
動作をみて分かるとおり、これは三項演算子、もっといえばif文のような役割を果たしています。そのため、名前もconditional typeとなっています。
さて、ここまで読んでいて「おや?」と混乱してきませんか?僕もとても困惑しました。なぜならExclude<keyof Person, "age">
は書き下すとkeyof Person extends "age" ? never : keyof Person
となるわけで、意味がわからないからです。ご安心ください。これであっています(この書き下しは間違えています)。このあときちんと説明します。
conditional typeは分配法則が適用される
conditional typeはT extends KのTがUnion型の場合、以下のように分配が起きます。
T extends K ? never : T -> T = (U1 | U2 | U3)とする -> (U1 extends K ? never : U1) | (U2 extends K ? never : U2) | (U3 extends K ? never : U3)
これを見ればExclude<keyof Person, "age">
でなぜうまく行くかはほとんど明らかですね。