この記事の対象
- TypeScriptでフォーム用の型、更新用の型、表示用の型…と、似たインターフェースを何個も手で書いている人
-
PartialPickOmitという名前は見たことがあるが、実際にどこで使うのか掴めていない人
「元の型」があるのに、なぜ何個も書くことになるのか
例えばユーザー情報を表す型があるとします。
interface User {
id: string;
name: string;
email: string;
createdAt: string;
}
これに対して、よくある3つの場面で型を「複製」しがちです。
// 更新フォーム用(一部の項目だけ送る)
interface UserUpdateInput {
name?: string;
email?: string;
}
// 新規作成フォーム用(idとcreatedAtはサーバーが決めるので除外)
interface UserCreateInput {
name: string;
email: string;
}
// 一覧表示用(emailは表示しない)
interface UserListItem {
id: string;
name: string;
createdAt: string;
}
一見動きますが、これだと User に項目を1つ追加・変更するたびに、3箇所を手で追いかけて直す必要が出てきます。忘れると型エラーにもならずに、フォームに古い項目が残ったまま、といった気づきにくいズレが生まれます。
パターン1: Partial<T> — 全部のプロパティを「あってもなくてもいい」にする
type UserUpdateInput = Partial<Pick<User, "name" | "email">>;
// { name?: string; email?: string; }
Partial<T> は T の全プロパティに ? を付けた型を作ります。更新フォームのように「変更したい項目だけ送ればいい」場面に向いています。
パターン2: Pick<T, K> — 必要なプロパティだけ抜き出す
type UserListItem = Pick<User, "id" | "name" | "createdAt">;
// { id: string; name: string; createdAt: string; }
Pick<T, K> は T から指定したプロパティ(K)だけを抜き出した型を作ります。一覧表示のように「元の型の一部だけ使いたい」場面に向いています。
パターン3: Omit<T, K> — 不要なプロパティだけ除外する
type UserCreateInput = Omit<User, "id" | "createdAt">;
// { name: string; email: string; }
Omit<T, K> は Pick の逆で、指定したプロパティ(K)を除いた型を作ります。新規作成フォームのように「サーバー側が決める項目だけ除きたい」場面では、Pick で必要な項目を列挙するより、Omit で不要な項目を除く方が書く量が少なく済みます。
効果: User を1箇所直せば、他の型も追従する
この3つに置き換えると、最初の3つの型定義は次のようになります。
type UserUpdateInput = Partial<Pick<User, "name" | "email">>;
type UserCreateInput = Omit<User, "id" | "createdAt">;
type UserListItem = Pick<User, "id" | "name" | "createdAt">;
この3つの型は、それぞれ User のどのキーを参照しているかが明示されているので、User 側でキーの名前を変える・消すといった変更をした時に効果がはっきり出ます。
interface User {
id: string;
fullName: string; // name から改名
email: string;
createdAt: string;
}
例えば name を fullName に変更すると、name というキーを直接指定している Pick<User, "id" | "name" | "createdAt"> や Partial<Pick<User, "name" | "email">> は、TypeScriptが「そんなキーは存在しません」と型エラーで教えてくれます。3つの型定義を別々に手で書いていた場合、この変更漏れは実行時までコンパイラが検知してくれません。
ただし Omit<User, "id" | "createdAt"> はこのケースでは反応しません。Omit が見ているのは「除外するキー」(id と createdAt)だけで、name は関与していないからです。name を fullName に変えても Omit 側はエラーにならず、結果の型が黙って fullName を含む形に変わるだけです。
さらに言うと、Omit は除外するキー(id や createdAt)自体を改名・削除した場合も、エラーになりません。Pick<T, K> の K は「T に実在するキーであること」がチェックされますが、Omit<T, K> の K にはその制約がなく、指定した文字列が T に存在するかどうかは見ていないからです。仮に id を userId に改名しても、Omit<User, "id" | "createdAt"> はコンパイルエラーにならず、id という除外指定は静かに意味を失い、本来除外したかったキーが結果の型に紛れ込みます。Omit は一見 Pick と対になる仕組みに見えますが、キーの変更をエラーで検知できるかどうかという点では Pick より弱い、という点は覚えておく価値があります。
キーを追加する場合も同様の非対称性があります。Pick で使う型(UserListItem など)は、指定したキー以外は見ていないので、User に新しいプロパティを足しても何も変わらず、エラーも出ません(表示したい場合は明示的にキーを追加する必要があります)。逆に Omit で使う型(UserCreateInput など)は、除外リストに入れていない新しいプロパティがそのまま自動的に含まれてしまいます。「サーバー側だけで使う内部フィールド」を後から User に追加した場合、Omit で作った作成用の型にそれがエラーなく混入する、という落とし穴になり得ます。追加のたびに、除外リストを見直す習慣が必要です。
AIに依頼するときも「派生元」を伝える
AIにフォーム用の型を書いてもらう時、「Userを参考にupdate用の型を作って」とだけ頼むと、まったく新しいインターフェースを一から書かれることがあります。「Partial<Pick<User, ...>> の形で」と、どのユーティリティ型を使ってほしいかを一言添えるだけで、User との関係を保ったコードが返ってきやすくなります。
型の設計をAIに丸投げするのではなく、「元の型との関係をどう表現してほしいか」を自分の言葉で伝えられるようになると、レビューする側としても変更に強いコードかどうかを見分けやすくなります。
※ 中上級者の方には易しすぎる内容かもしれません。初心者の知り合いへの紹介や、社内研修の参考としてどうぞ。
▶ まず、教材で最初に作るページを見てみる(インストール不要・無料)
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を全20セッションで体系化した教材付きプランを運営しています。面談なしで、申し込んだその日から始められます → https://menta.work/plan/20251?ref=qiita
自分の手元で動かしたい方は、体験版のリポジトリからどうぞ(最初の数セッション分)→ next-ai-camp-trial(GitHub・無料)