Exclude<T, U> はユニオン型からメンバーを取り除き、Extract<T, U> は一致するメンバーだけを残し、NonNullable<T> は null と undefined を取り除きます。3つともユニオン型を絞り込むもので、オブジェクトのプロパティには触れません。
ほかのユーティリティ型と同じく、これらはコンパイル時にしか存在しません。最後の行はコンパイラーに拒否されます。実行されるJavaScriptは Visible が何かを知りません。
Exclude、Extract、NonNullable の一覧
| 型 | 残すもの | 例 | 結果 |
|---|---|---|---|
Exclude<T, U> | U に代入できない T のメンバー | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Extract<T, U> | U に代入できる T のメンバー | Extract<"a" | "b" | "c", "a" | "z"> | "a" |
NonNullable<T> | null と undefined 以外のすべて | NonNullable<string | null | undefined> | string |
大事なのは「U に代入できる」という部分です。U は正確なメンバーの一覧である必要はなく、複数のメンバーが当てはまる広い型でもかまいません:
形でユニオン型のメンバーを選ぶ
判別可能なユニオン型に対して部分的な形を渡して Extract を使うと、その形を持つバリアントを取り出せます。自分で書いていないユニオン型の1つのバリアントに名前を付けるときの定番の方法です:
形 { type: "click" } はクリックのバリアントだけのスーパータイプなので、Extract が残すのはそれだけです。複数のバリアントに一致する形(たとえば2つのバリアントが { x: number } を持つ場合)なら、それらをすべて残します。
NonNullable
NonNullable<T> は、!== null && !== undefined のチェックを型で表したものです。省略可能なものから型を作るときによく出てきます:
User["manager"] は { name: string } | null | undefined(? が undefined を追加します)で、NonNullable がその両方を取り除きます。TypeScript 5.5 以降はコンパイラーが自分でその型述語を推論するので、raw.filter((s) => s != null) はすでに string[] 型になります。明示的な s is NonNullable<typeof s> は意図を見えるようにし、古いバージョンでも動きます。定義は type NonNullable<T> = T & {} です。空のオブジェクト型 {} は null と undefined 以外のすべての値を受け付けるので、それとの交差型を取るとちょうどその2つが取り除かれます。
定義のしくみ
Exclude と Extract は、標準ライブラリでは1行の条件型です:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
T がユニオン型のとき、条件型は各メンバーに別々に適用され(分配され)、never のメンバーは結果から消えます。そのため Exclude<"a" | "b", "a"> は ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b") となり、never | "b"、つまり "b" になります。分配については 条件型 のページで詳しく説明しています。
Exclude と Omit、Extract と Pick
どちらのペアも何かを「取り除く」か「残す」ので、名前を混同しがちです。違いは何に対して働くかです:
| 対象 | 取り除く・残すもの | 例 | |
|---|---|---|---|
Exclude<T, U> | ユニオン型 | ユニオン型のメンバー | Exclude<"a" | "b", "a"> → "b" |
Extract<T, U> | ユニオン型 | ユニオン型のメンバー | Extract<"a" | "b", "a"> → "a" |
Omit<T, K> | オブジェクト型 | プロパティ | Omit<User, "password"> |
Pick<T, K> | オブジェクト型 | プロパティ | Pick<User, "name"> |
オブジェクト型に Exclude を使うのはよくある間違いで、しかもエラーになりません:
Account は "password" に代入できないので、Exclude はそれを丸ごと残します。Omit 自体はキーに Exclude を適用して作られています。Omit<T, K> は Pick<T, Exclude<keyof T, K>> です。詳しくは Omit を参照してください。
より厳密な Exclude
Exclude の2番目の型パラメーターはどんな型でも受け付けるので、打ち間違えても何も取り除かれず、エラーも出ません。制約を加えれば、除外するものが本当にユニオン型に含まれているかをコンパイラーがチェックします:
index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.
つづりを直せばコンパイルでき、Strict は "published" | "archived" になります。同じ方法は Extract や厳密な Omit(K extends keyof T)にも使えます。
よくある質問
TypeScriptの Exclude は何をしますか?
Exclude<T, U> は、ユニオン型 T から U に代入できるメンバーをすべて取り除きます。Exclude<"a" | "b" | "c", "a"> は "b" | "c" です。複数のメンバーを取り除くには、U にユニオン型を渡します: Exclude<T, "a" | "b">。
Exclude と Extract の違いは何ですか?
正反対です。Exclude<T, U> は T のメンバーのうち U に代入できないものを残し、Extract<T, U> は代入できるものだけを残します。同じ T と U なら、2つの結果を合わせると T になります。
Exclude と Omit の違いは何ですか?
Exclude はユニオン型に対して働き、ユニオン型のメンバーを取り除きます。Omit はオブジェクト型に対して働き、プロパティを取り除きます。Exclude<User, "password"> は意味のあることをせず(User を返します)、Omit<User, "password"> はそのプロパティのないオブジェクト型を返します。Omit は Exclude をもとに作られていて、keyof T からキーを除外しています。
TypeScriptの NonNullable は何をしますか?
NonNullable<T> は型から null と undefined を取り除きます: NonNullable<string | null | undefined> は string です。{} は null と undefined 以外のすべての値を受け付けるので、T & {} として定義されています。
存在しないメンバーを指定しても Exclude がエラーにならないのはなぜですか?
2番目の型パラメーターに制約がないので、どんな型でも受け付け、Exclude<Status, "drafft"> のような打ち間違いは黙って何も取り除きません。より厳密な版 type StrictExclude<T, U extends T> = Exclude<T, U> を定義すれば、U が T の一部であることをコンパイラーがチェックします。