【TypeScript】Omitを完全理解|Pickとの違いと3つの落とし穴
「既存のUser型からpasswordだけを除いた型がほしい」——そんな場面でまず手が伸びるのがTypeScriptのOmitです。
この記事では、Omitの基本的な書き方から、よく混同されるPickとの違い、そして「除外したはずなのに効かない」と感じる落とし穴の正体までを、実際に動くコードと型エラーで整理します。読み終えるころには、目の前の型を見て「ここはOmit、ここはPick」と迷わず選べて、実行時に値が残る・タイポが素通りする・union型で崩れるといったハマりどころを先回りで避けられるようになります。
この記事は次のような方におすすめです。
- 基本型とinterface/typeは理解していて、ユーティリティ型を使い始めた中級者の方
- Reactのpropsやfetchのレスポンス型を整形するのにOmitを使いたい方
- OmitとPickのどちらで書くべきか毎回迷ってしまう方
- Omitが「効かない」と感じた経験があり、その理由を知りたい方
基本構文と判断基準、そして落とし穴の回避策までを一度に押さえることで、型整形のコードを安全かつ読みやすく書けるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
4年間ずっと年収260万だったエンジニアが、転職で510万になるまでの全記録
TypeScript Omitとは?型からプロパティを除外するユーティリティ型
Omitは、あるオブジェクト型から指定したプロパティを取り除いた新しい型を作るユーティリティ型です。元の型を直接書き換えるのではなく、Omit<元の型, 除外するキー> と書くだけで「○○以外」の型が手に入ります。
たとえばUser型からpasswordを除いた型は、次のように書きます。
interface User {
id: number;
name: string;
password: string;
}
type PublicUser = Omit<User, "password">;
// 結果: { id: number; name: string }
const user: PublicUser = { id: 1, name: "Taro" };
user.password; // 例:TS2339 Property 'password' does not exist on type 'Omit<User, "password">'.
PublicUserからはpasswordが消えているため、そのプロパティにアクセスしようとするとコンパイル時にTS2339が報告されます。型の上では確実に除外されている、というのがOmitの基本動作です。
一点だけ先に押さえておきたいのは、Omitが影響するのは型の世界だけで、実行時のオブジェクトに入っている実際の値は変えないという点です。PublicUser型を満たすと注釈しても、元のオブジェクトにpasswordの値が残っていれば実行時にはそのまま存在します。この挙動は後半の落とし穴で詳しく扱います。
基本構文と型引数(T と Keys)
Omit<T, Keys>の型引数は2つです。Tには元になるオブジェクト型を、Keysには除外したいプロパティ名を文字列リテラルで渡します。
interface Article {
id: number;
title: string;
body: string;
}
// 単一キーを除外
type Draft = Omit<Article, "id">;
// 結果: { title: string; body: string }
// 複数キーは union で渡す
type TitleOnly = Omit<Article, "id" | "body">;
// 結果: { title: string }
除外キーが1つなら"id"のように単独で、複数なら"id" | "body"のように文字列リテラルのunionで指定します。Omit<T, Keys>の山括弧は型引数を受け取るジェネリクスの記法で、Tに渡した型に応じて結果の型が決まる仕組みです。
<T>のような型引数(ジェネリクス)の仕組みそのものをまだ整理できていない方は、こちらで基礎から確認できます。
複数のプロパティをまとめて除外する
2つ以上のプロパティを一度に除外したいときは、除外キーを縦棒|でつないだunionとして渡します。
interface User {
id: number;
name: string;
password: string;
token: string;
}
type SafeUser = Omit<User, "password" | "token">;
// 結果: { id: number; name: string }
ここで気をつけたいのが、Omit<User, "password", "token">のようにカンマで区切って第3引数のように並べる書き方は無効だという点です。Omitが受け取る型引数はTとKeysの2つだけなので、複数キーは必ず1つのunionにまとめる必要があります。
標準ライブラリでのOmitの実装を読み解く
Omitは魔法ではなく、PickとExcludeを組み合わせて定義されています。TypeScript標準ライブラリ(lib.es5.d.ts)では、おおよそ次の形で書かれています(定義の詳細はTypeScriptバージョンにより異なる場合があります)。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
分解すると、次のように読み解けます。
keyof T:元の型Tが持つキー名すべてのunionを取り出すExclude<keyof T, K>:そのキー名のうち、除外対象Kに一致するものを取り除くPick<T, ...>:残ったキー名だけをTから抜き出して新しい型を作る
つまりOmitは、「Tの全キーから除外キーを引き、残りをPickで抽出する」という二段構えで実装されているわけです。ここでKの制約がkeyof any(=string | number | symbol)であってkeyof Tではない点が、後で触れる「存在しないキーを指定してもエラーにならない場合がある」落とし穴の根っこになります。実装を知っておくと、落とし穴の症状が理屈で腑に落ちます。
Omitを使うべき場面と判断基準
Omitを選ぶか迷ったときの基準はシンプルで、「取り除きたいキーが少数ならOmit、残したいキーが少数ならPick」と考えると判断が速くなります。OmitとPickは「除外」と「抽出」という逆向きの操作で、同じ結果を両方から書けるためです。
同じPublicUser型を、OmitとPickの両方で書き比べてみます。
interface User {
id: number;
name: string;
password: string;
}
// 除外したいキー(password)を指定
type ByOmit = Omit<User, "password">;
// 残したいキー(id, name)を指定
type ByPick = Pick<User, "id" | "name">;
// どちらも { id: number; name: string }
両者の性質を比較表で整理します。
| 観点 | Omit | Pick |
|---|---|---|
| 操作 | 指定キーを除外する | 指定キーを抽出する |
| 指定キーの意味 | 「消したい」キー | 「残したい」キー |
| 向くケース | 元の型からごく一部を外したい | 元の型からごく一部だけ使いたい |
| プロパティ追加時の挙動 | 追加分は自動で残る | 追加分は自動で含まれない |
判断に迷ったら、次のフローで考えると整理できます。
- 除外したいキーが少数で、新しく増えるプロパティは基本的に残したい → Omit
- 残したいキーが少数で、増えたプロパティを勝手に含めたくない → Pick
ポイントはプロパティが将来増えたときの「安全側」がどちらかという観点です。元の型にフィールドが追加されたとき、Omitは追加分を自動で含み、Pickは含みません。たとえば公開用の型をPublicUserのように作る場合、機密フィールドの追加漏れを防ぎたいならPickで「見せてよいキー」を明示する方が安全な一方、追加情報は基本的に公開してよいならOmitで「隠すキー」だけ管理する方が保守が楽です。文脈に応じて安全側を選びましょう。
Omitを使う実践シーン(API・Reactのprops)
Omitが現場で効くのは、「元の型はあるが、その一部を外した別の型がほしい」場面です。代表的な3つを実コードで見ていきます。
1つ目は、機密フィールドを外したレスポンス型です。
interface UserRecord {
id: number;
name: string;
password: string;
createdAt: Date;
}
// DBの型から機密フィールドを外して返却用に
type UserResponse = Omit<UserRecord, "password">;
// 結果: { id: number; name: string; createdAt: Date }
2つ目は、サーバーが採番する項目を除いた入力型です。
// id と createdAt はサーバー側で付与するので入力からは外す
type CreateUserInput = Omit<UserRecord, "id" | "createdAt">;
// 結果: { name: string; password: string }
3つ目は、ReactのProps拡張で既存属性を差し替えるパターンです。標準のonClickを独自シグネチャに置き換えたいとき、衝突を避けるためにいったんOmitで外します。
import type { ButtonHTMLAttributes } from "react";
type MyButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick"> & {
onClick: (label: string) => void;
};
// 標準の onClick を外し、独自の onClick に差し替えた型になる
いずれも、元の型を起点にしつつ「ここだけ違う」という差分を型で表現できるのがOmitの利点です。
OmitやPickで切り出す方針に変えてからは、同じプロパティ定義を繰り返し書く場面が減り、ベースの型を一か所直せば各メソッドの型も追従するようになりました。冗長な型定義をまとめたいときに、Omitは特に手になじみました。
型の上書き(プロパティ差し替え)パターン
既存型の一部プロパティだけ型を変えたいとき、いったんOmitでそのキーを外してから交差型&で新しい型を足すのが定番のイディオムです。
interface ApiUser {
id: string;
name: string;
}
// id を string から number に差し替える
type DbUser = Omit<ApiUser, "id"> & { id: number };
// 結果: { name: string; id: number }
なぜ& { id: number }を足すだけでは駄目かというと、交差型で同名プロパティの型が衝突すると意図どおりに上書きされないためです。先に元のidをOmitで取り除いておくことで、新しいid: numberがクリーンに反映されます。型の「部分的な差し替え」は、この除外してから足すパターンで安全に書けます。
Omitでハマる落とし穴と回避策
Omitは便利ですが、「除外したのに効いていない」と感じる場面があります。その多くは型と値の区別、実装上の制約、union型の扱いに起因します。症状を起点に、代表的な3つの落とし穴と回避策を見ていきましょう。
落とし穴1:実行時には値が消えない(型と値の区別)
最初の落とし穴は、型としてはOmitで除外しても、実行時のオブジェクトからはその値が消えないというものです。
interface User {
id: number;
name: string;
password: string;
}
const raw: User = { id: 1, name: "Taro", password: "secret" };
const publicUser: Omit<User, "password"> = raw;
console.log((publicUser as User).password); // "secret" が残っている
publicUserは型の上ではpasswordを持たないことになっていますが、中身はrawそのものなので、実行時にはpasswordの値が残っています。Omitはあくまで型の表現を変えるだけだからです。
実際に値も落としたいなら、分割代入で新しいオブジェクトを作って取り除きます。
const { password, ...safe } = raw;
// safe: { id: number; name: string } で password の値も含まれない
ただしdeleteで同じことをしようとする場合は注意が必要です。strictNullChecksが有効なとき、passwordのような必須プロパティをdelete raw.passwordのように直接消そうとするとTS2790の型エラーになります(deleteの対象はany/unknown/undefinedを含む任意プロパティである必要があります)。そのため、値を落とす基本は上記の分割代入とし、deleteは任意プロパティや型調整が前提のケースに留めるのが安全です。
「型で消えるから値も消える」という思い込みが、この落とし穴の入り口です。型の除外と値の除外は別物だと意識しておきましょう。
落とし穴2:存在しないキーを指定してもエラーにならない場合
2つ目は、元の型に存在しないキーを除外指定しても、エラーにならないことがあるという落とし穴です。
interface User {
id: number;
name: string;
}
// "nmae" はタイポ(正しくは name)だが…
type Result = Omit<User, "nmae">;
// 結果: { id: number; name: string }(何も除外されず、エラーも出ないことがある)
なぜ静かに素通りするかは、先ほど読み解いた標準実装で説明できます。OmitのKの制約はkeyof any(=string | number | symbol)であってkeyof Tではないため、Tに存在しないキー名を渡しても型制約には違反しないのです。結果としてExclude<keyof T, K>は何も引かず、元の型がそのまま返ります。
厳格にタイポを検出したい場合は、Kをkeyof Tに制約した自作のOmitStrictを使う方法があります。
type OmitStrict<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Bad = OmitStrict<User, "nmae">;
// 例:TS2344 Type '"nmae"' does not satisfy the constraint 'keyof User'. (TypeScriptバージョンにより文言が異なる場合あり)
OmitStrictならK extends keyof Tの制約により、存在しないキーを渡した時点でコンパイルエラーになります。なお、標準Omitの存在しないキーに対する挙動やエラー文言はTypeScriptのバージョンによって異なる場合があるため、手元の環境で確認してから運用するのが安全です。
落とし穴3:union型へのOmitが分配されない
3つ目は、union型に素のOmitをかけると、各メンバーごとに処理されず型が崩れる落とし穴です。
type Admin = { role: "admin"; id: number; secret: string };
type Guest = { role: "guest"; id: number };
type Cleaned = Omit<Admin | Guest, "id">;
// 結果(期待と異なる): { role: "admin" | "guest" }
Admin | GuestにOmitをかけると、TypeScriptは両者の共通キーだけを見て処理するため、Admin固有のsecretまで落ちてしまい、roleしか残らない型になります。各メンバーを保ったまま除外したいときは、条件付き型で分配(distributive)させる自作のDistributiveOmitを使います。
type DistributiveOmit<T, K extends keyof any> = T extends any
? Omit<T, K>
: never;
type CleanedOK = DistributiveOmit<Admin | Guest, "id">;
// 結果: Omit<Admin, "id"> | Omit<Guest, "id">
T extends any ? ... : neverという条件付き型は、union型を受け取ると各メンバーに分配して評価されます。これによりメンバーごとに個別にOmitが適用され、secretを保ったままidだけを除外できます。分配の挙動はTypeScriptの条件付き型(distributive conditional types)の仕様に基づきますが、複雑なunionでは結果が想定とずれることもあるため、結果型を確認しながら使いましょう。
よくある質問
Omitはどんな場面で使うべき?
除外したいキーが少数ならOmit、残したいキーが少数ならPickを選ぶのが基本の判断軸です。元の型から「ここだけ消したい」ときはOmitが簡潔に書けます。書き分けの詳細は、Pickの使い方と合わせて確認すると定着します。
Omitで複数のプロパティを除外できる?
できます。除外キーを縦棒|でつないだunionとして渡します。たとえばOmit<User, "password" | "token">と書けば、passwordとtokenの両方を一度に除外した型が得られます。
Omitしたのに実行時に値が残るのはなぜ?
Omitは型の表現を変えるだけで、実行時のオブジェクトの値には影響しないためです。実際に値も取り除くには、const { password, ...safe } = obj のような分割代入で新しいオブジェクトを作り、値そのものを落とす必要があります。deleteでも値は消せますが、strictNullChecks有効時に必須プロパティを直接deleteすると型エラー(TS2790)になるため、基本は分割代入を使うのが安全です。
存在しないキーをOmitに渡すとどうなる?
標準のOmitは除外キーの制約がkeyof anyのため、存在しないキーを渡してもエラーにならず何も除外されない場合があります。タイポを検出したいならK extends keyof Tに制約した自作型を使います(挙動はバージョン依存のため要確認)。
【付録】さらに学びを深めるためのリソース
さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。
おすすめの書籍
ゼロからわかる TypeScript入門
技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。
変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。
プロを目指す人のためのTypeScript入門
技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。
オンラインで参照できる公式ドキュメント
TypeScript公式ハンドブック
https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。
TypeScript Deep Dive
https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。
TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。
まとめ – Omitで型から除外し、Pickと落とし穴を使い分ける
この記事のポイントをまとめます。
- Omitは型から指定プロパティを除外するユーティリティ型で、
Omit<T, Keys>の形で書く - Pickは抽出、Omitは除外という逆向きの操作で、除外したいキーが少数ならOmitが向く
- 標準Omitは
Pick<T, Exclude<keyof T, K>>で実装されており、これが存在しないキーの落とし穴の根拠になる - 実行時には値が消えない・存在しないキーが素通りする・union型で分配されない、の3つの落とし穴は回避策とセットで押さえる
- 値も消すなら分割代入/delete、タイポ検出はOmitStrict、union対応はDistributiveOmitで対処する
Omitは「除外」を型で表現できる強力な道具ですが、型と値の区別さえ意識すれば、APIレスポンスやpropsの整形を安全に書けるようになります。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






