こんにちは、かつコーチです。
前回はジェネリクスの制約(extends)を扱い、型パラメータに条件をつける方法を紹介しました。
今回は、その仕組みを使って作られているユーティリティ型(既存の型を加工して新しい型を作る組み込み機能)を紹介します。
「同じようなインターフェースを何個も定義していて、正直コピペが多い」と感じている人にこそ読んでほしい内容です。
ユーティリティ型とは?
既存の型を”加工”するための機能
ユーティリティ型とは、既存の型を元に「一部だけ変えた型」を自動生成してくれる、TypeScriptに標準で用意された仕組みです。
型定義を1から書き直すのではなく、既存の型を材料にして加工するイメージを持つと理解しやすいです。
interface User {
id: number;
name: string;
email: string;
}
この User 型があるとき、「更新フォーム用に全部オプショナルにしたい」「一覧表示用に一部プロパティだけ欲しい」といった場面が実務では頻繁に出てきます。
そのたびに似たインターフェースを手書きしていると、片方だけ修正し忘れる、といった事故が起きやすくなります。
なぜ必要なのか
ユーティリティ型を使うと、元の型を1箇所で管理しながら、用途ごとの派生型を安全に作れます。
User の定義を直したときに、派生型も自動で追随してくれるのが最大のメリットです。
Partial:全プロパティを任意にする
基本の使い方
Partial<T> は、指定した型のすべてのプロパティをオプショナル(値がなくてもよい、任意のプロパティ)に変換します。
interface User {
id: number;
name: string;
email: string;
}
function updateUser(id: number, changes: Partial<User>): void {
console.log(`ユーザー${id}を更新: `, changes);
}
updateUser(1, { name: "かつコーチ" }); // name だけでOK
updateUser(2, { email: "new@example.com" }); // email だけでもOK
更新処理では「変更したいプロパティだけ渡したい」ことがほとんどなので、Partial<User> にしておけば、必須プロパティを毎回全部渡す必要がなくなります。
つまずきやすいポイント:Partialの使いすぎ
❌ Before:新規作成の引数までPartialにしてしまう
function createUser(user: Partial<User>): void {
console.log(user.id.toString()); // エラー:Object is possibly 'undefined'.
}
私が実際にやってしまったのが、新規作成用の関数にまで Partial<User> を使ってしまうミスです。
「更新のときに便利だったから」という理由だけで安易に使い回した結果、id が undefined かもしれない扱いになり、意図と違うエラーに悩まされました。
✅ After:用途に応じて元の型とPartialを使い分ける
function createUser(user: User): void {
console.log(user.id.toString()); // OK:User は id が必須
}
function updateUser(id: number, changes: Partial<User>): void {
console.log(`ユーザー${id}を更新: `, changes);
}
「新規作成=全項目必須」「更新=一部項目だけでよい」というように、業務上の意味に合わせて型を選び直すのが正しい使い方でした。
Pick:一部プロパティだけ取り出す
基本の使い方
Pick<T, キー名> は、指定した型から特定のプロパティだけを取り出した型を作ります。
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserPreview = Pick<User, "id" | "name">;
const preview: UserPreview = {
id: 1,
name: "かつコーチ",
};
一覧画面では password のような機密情報を扱いたくないケースが多く、Pick で必要なプロパティだけに絞った型を用意しておくと、うっかり不要なデータを扱う事故を防げます。
複数プロパティを組み合わせる
キー名は |(ユニオン型)でつなげることで、複数指定できます。
type UserContact = Pick<User, "name" | "email">;
const contact: UserContact = {
name: "かつコーチ",
email: "info@example.com",
};
必要なプロパティだけを列挙するので、型定義を見ただけで「この画面はどの情報を扱うのか」が一目で分かるようになります。
Omit:一部プロパティだけ除外する
基本の使い方
Omit<T, キー名> は Pick の逆で、指定したプロパティ以外を残した型を作ります。
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserWithoutPassword = Omit<User, "password">;
const publicUser: UserWithoutPassword = {
id: 1,
name: "かつコーチ",
email: "info@example.com",
};
プロパティ数が多い型から「これだけ除きたい」というときは、Pick で残す項目を全部書き並べるより、Omit で除外したい項目だけ書くほうが圧倒的に楽です。
PickとOmitの使い分け
| ユーティリティ型 | 向いている場面 |
|---|---|
Pick<T, K> | 元の型のプロパティ数が多く、必要な項目が少数のとき |
Omit<T, K> | 元の型のプロパティのうち、除きたい項目が少数のとき |
どちらも本質的にはプロパティの取捨選択なので、「残したい数」と「除きたい数」のどちらが少ないかで選ぶと、コードがすっきりします。
応用・一歩先の使い方
Partial・Pick・Omitを組み合わせる
これら3つは組み合わせて使うこともできます。
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserUpdateForm = Partial<Omit<User, "id" | "password">>;
const form: UserUpdateForm = {
name: "新しい名前", // email は省略可能
};
Omit<User, "id" | "password"> で更新不可なプロパティを除いたあと、Partial で全体を任意にすることで、「更新フォーム用の型」を1行で表現できます。
元の User 型を直接いじらずに、目的に応じた派生型を安全に作れるのが、ユーティリティ型の強みです。
Requiredで逆にオプショナルを必須へ
余談ですが、Partial の逆にあたる Required<T> もよく使います。
interface Options {
timeout?: number;
retry?: number;
}
function runTask(options: Required<Options>): void {
console.log(`timeout: ${options.timeout}, retry: ${options.retry}`);
}
「デフォルト値を適用したあとは、必ず全プロパティが埋まっている」ことを型で保証したいときに Required<T> が役立ちます。
まとめ
この記事のポイント
Partial<T>はすべてのプロパティを任意に変換する(更新処理と相性がよい)Pick<T, K>は指定したプロパティだけを取り出すOmit<T, K>は指定したプロパティ以外を残す- 用途と関係なく便利だからと
Partialを使い回すと、必須のはずのプロパティまでundefined扱いになり事故のもとになる - 複数のユーティリティ型は組み合わせて使え、目的に応じた派生型を1行で表現できる
次に読むべき記事
ユーティリティ型で型を加工できるようになったら、次はunion型を安全に扱うための「型ガード」を学んでいきましょう。
→ 次の記事:型ガードでunion型を安全に絞り込む