Menu

TypeScriptのExclude・Extract・NonNullableを解説

Exclude、Extract、NonNullable はユニオン型のメンバーを絞り込むユーティリティ型です。それぞれが何を残し何を取り除くか、形でユニオン型のメンバーを選ぶ方法、条件型による定義、Omit や Pick との違いを解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

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 の一部であることをコンパイラーがチェックします。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める