Omit<T, K> は、T のプロパティから K に挙げたキーを除いたすべてを持つオブジェクト型を作る組み込みのユーティリティ型です。典型的な使い方は、password を持つ User 型と、それを持たない公開用の型です。
PublicUser は User と同期したままです。User にフィールドを追加すれば PublicUser にも現れ、password は除外されたままです。Omit は型にしか働きません。実際のオブジェクトからプロパティを取り除いているのは、toPublic の中の分割代入です。
複数のキーを Omit する
2番目の引数はユニオン型なので、必要なだけのキーを | で並べます:
「サーバーが生成するフィールドを除いた型」は、実際のコードでいちばんよく使われる Omit です。モデルの型を1つ持ち、作成フォームやAPIリクエストの入力の形を Omit で作ります。
Omit の定義
Omit はほかの2つのユーティリティ型から作られています:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
内側から読みましょう。keyof T はすべてのキーのユニオン型、Exclude<keyof T, K> はそのユニオン型から不要なキーを取り除き、Pick が残りのキーを残します。つまり Omit<User, "password"> は Pick<User, "id" | "name" | "email"> です。Pick は T のキーに対してマップするので、残ったプロパティの readonly や ? の修飾子は保たれます。
制約に注目してください。K extends keyof T ではなく K extends keyof any です。この点が次のセクションの話題です。
Omit はキーが存在するかをチェックしない
keyof any は string | number | symbol なので、Omit は T が持っていないキーも含め、どんなキーでも受け付けます。打ち間違いもコンパイルでき、何も取り除かれません:
どこにもエラーは出ず、パスワードはJSONで外に出てしまいます。このゆるい制約は、TypeScript 3.5 で Omit が追加されたときに意図的に選ばれたものです。チームの理由は次のとおりです。ジェネリックなコードでは、除外するキーが T から来ないことが多いこと(2つのオブジェクトをマージする関数は残余引数を Omit<T1, keyof T2> と型付けできます)、Exclude も2番目の型パラメーターを制約していないこと、そして制約付きの版にすると、すでに多くのプロジェクトが手書きしていた Omit 型を壊してしまうことです。日常のコードでは、より厳密な版で打ち間違いを捕まえられます:
index.ts(10,36): error TS2344: Type '"pasword"' does not satisfy the constraint 'keyof User'.
index.ts(11,7): error TS2741: Property 'password' is missing in type '{ id: number; name: string; }' but required in type 'PublicUser'.
重要なのは最初のエラーです。キーが keyof User と照らし合わせてチェックされています。2つ目はそこから生じたもので、何も除外されていないので PublicUser はまだ password を要求しています。つづりを "password" に直せばコンパイルできます。多くのコードベースでは、共有の型ファイルにこれを一度定義し、Omit の代わりに使っています。
プロパティの型を上書きする
インターフェースは、別のインターフェースを拡張しつつプロパティを互換性のない型に変えることはできません。interface ApiUser extends User { id: string } は error TS2430: Interface 'ApiUser' incorrectly extends interface 'User' で失敗します。先にプロパティを Omit してから、もう一度宣言しましょう:
型エイリアスなら、同じことは交差型になります: type ApiUser = Omit<User, "id"> & { id: string }。Omit がないと、交差型 User & { id: string } は id に number & string、つまり never の型を与えてしまいます。
ユニオン型への Omit
Omit は分配されません。ユニオン型に使うと、ユニオン型全体の keyof から始めるので、すべてのメンバーが共有するキーだけが含まれ、各メンバー固有のプロパティは消えてしまいます:
index.ts(8,39): error TS2353: Object literal may only specify known properties, and 'radius' does not exist in type 'NewShape'.
NewShape は { kind: "circle" | "square" } になってしまいました。各メンバーから別々に除外するには、分配条件型の中で Omit を適用します:
T extends unknown ? ... : never は常に true です。条件型をユニオン型に分配させるためだけにあります。それがなぜ動くかは 条件型のページ で説明しています。除外する元の型が判別可能なユニオン型なら、いつでも DistributiveOmit を使いましょう。
ネストしたプロパティを Omit する
Omit はトップレベルのキーしか見ないので、Omit<Settings, "editor.wordWrap"> は何も取り除きません(そして、上で見たように文句も言いません)。1段下のプロパティを取り除くには、親を Omit してから、それ自身の Omit を付けて追加し直します:
Settings["editor"] はインデックスアクセス型で、ネストしたオブジェクト型に名前を付けて Omit が扱えるようにしています。
実行時にプロパティを取り除く
Omit が変えるのは型だけです。プロパティのないオブジェクトを作るには、分割代入で取り除くか、戻り値の型が Omit の小さなジェネリックなヘルパーを書きます:
ヘルパーは K を keyof T に制約しているので、キーを打ち間違えると呼び出し箇所でエラーになります。コピーは浅いので、ネストしたオブジェクトは元のオブジェクトと共有されます。
Omit、Pick、Exclude の比較
| 対象 | 残すもの | 例 | 結果 | |
|---|---|---|---|---|
Omit<T, K> | オブジェクト型 | K 以外のすべてのプロパティ | Omit<User, "password"> | { id; name; email } |
Pick<T, K> | オブジェクト型 | K にあるプロパティだけ | Pick<User, "id" | "name"> | { id; name } |
Exclude<U, M> | ユニオン型 | M に代入できないユニオン型のメンバー | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Omit と Pick のどちらを選ぶかは、元の型が大きくなったときにどうなってほしいかで決めます。Omit は新しいプロパティを自動的に含めるので、「秘密以外すべて」に向いています。Pick は新しいプロパティを決して含めないので、「ちょうどこのフィールド」に向いていて、サーバーから外に出るものにはより安全です。Pick はキーを keyof T と照らし合わせてチェックしますが、Omit はしません。
よくある質問
TypeScriptの Omit は何をしますか?
Omit<T, K> は、T のプロパティのうち、キーが K に含まれるものを除いたすべてを持つ新しいオブジェクト型を作ります。Omit<User, "password"> は password のない User です。変わるのは型だけで、実行時にオブジェクトから何かを取り除くことはありません。
TypeScriptで複数のプロパティを Omit するには?
2番目の引数にキーのユニオン型を渡します: Omit<User, "password" | "createdAt" | "updatedAt">。ユニオン型のすべてのキーが取り除かれます。
Omit と Exclude の違いは何ですか?
Omit はオブジェクト型からプロパティを取り除きます: Omit<{ a: 1; b: 2 }, "a"> は { b: 2 } です。Exclude はユニオン型からメンバーを取り除きます: Exclude<"a" | "b", "a"> は "b" です。Omit は Exclude を使って実装されています: Pick<T, Exclude<keyof T, K>>。
存在しないキーを指定しても Omit がエラーにならないのはなぜですか?
キーの型パラメーターの制約が keyof T ではなく keyof any(任意の文字列、数値、シンボル)だからです。そのため Omit<User, "pasword"> のような打ち間違いもコンパイルでき、何も取り除かれません。知らないキーでエラーを出すには、type StrictOmit<T, K extends keyof T> = Omit<T, K> を定義します。
TypeScriptで実行時にオブジェクトからプロパティを取り除くには?
残余要素を使った分割代入を使います: const { password, ...rest } = user;。TypeScriptは rest を password のないオブジェクト、つまり Omit<User, "password"> と同じ形として型付けし、新しいオブジェクトからはプロパティが本当になくなります。