TypeScript:ユーティリティ型
1学習の目的
- Partial・Pick・Omit などの組み込み型を使い、既存の型を加工して新しい型を作れるようになる。同じ項目を何度も書き直す必要がなくなる。
- 型の重複を避ける設計ができるようになる。元の型を1箇所直せば、そこから派生した型すべてに反映される仕組みを作れる。
2基礎解説
「更新用に全項目を省略可能にした型」「一覧表示用に一部だけ取り出した型」——元の型があるのに、似た型を何度も書くのは無駄です。TypeScriptには変換用の型が最初から用意されています。
| 型 | 効果 | 用途 |
|---|---|---|
| Partial<T> | 全項目を省略可能に | 更新処理 |
| Pick<T, K> | 指定した項目だけ | 一覧表示 |
| Omit<T, K> | 指定した項目を除く | 新規作成(id無し) |
| Record<K, V> | キーと値の対応表 | 集計結果 |
- Partial<T> は全項目を ? 付きにする。「変更したい項目だけを渡す」更新処理で使う。
- Pick<T, "a" | "b"> は必要な項目だけを抜き出す。一覧表示など、全項目が要らない場面で使う。
- Omit<T, "id"> は指定した項目を除く。「新規作成時はidがまだ無い」という状況をそのまま型にできる。
- Record<string, number> はキーと値の対応表。TS10・TS11の集計で使った型で、オブジェクトを辞書として使うときに書く。
- 元の型を変えれば、派生した型すべてに反映される。Product に項目を足せば Partial<Product> も自動で追随する——これが重複を書かない最大の利点。
現場使用例:APIの更新リクエスト(Partial)、一覧APIのレスポンス(Pick)、新規登録フォーム(Omit)、集計結果(Record)。
type Product = {
id: number;
name: string;
price: number;
category: string;
};
// Partial:全項目が省略可能になる(更新用)
const patch: Partial<Product> = { price: 1000 }; // price だけでOK
// Pick:必要な項目だけ抜き出す(一覧用)
type Summary = Pick<Product, "id" | "name">;
const s: Summary = { id: 1, name: "ノートPC" };
// Omit:指定した項目を除く(新規作成用)
type NewProduct = Omit<Product, "id">;
const draft: NewProduct = {
name: "新商品", price: 5000, category: "PC",
};
// Required:省略可能を全部必須にする
type Options = { a?: number; b?: string };
const opts: Required<Options> = { a: 1, b: "x" };
// Readonly:全項目を変更不可に
const fixed: Readonly<Product> = {
id: 1, name: "PC", price: 100, category: "PC",
};
// fixed.price = 200; → エラー
// Record:キーと値の対応表
const counts: Record<string, number> = { PC: 2, 周辺機器: 3 };
// keyof:型のキーを取り出す
type Keys = keyof Product; // "id" | "name" | "price" | "category"
3基本ドリル(10問)
出力 == {"price":1000}
const patch: Partial
type Product = { id: number; name: string; price: number };
const patch: Partial<Product> = { price: 1000 };
console.log(JSON.stringify(patch));
Partial は全項目を省略可能にする。更新処理では「変更したい項目だけ渡す」ことが多いので、この型が便利になる。
A. 一部の項目を取り出す B. 全項目を省略可能にする C. 全項目を必須にする D. 変更不可にする 選択★☆☆無料
解答 == B
Partial は「部分的」という意味。
B
すべての項目に ? が付いた型になる。TS08で1つずつ ? を書いていた作業を、まとめて行える。
出力 == {"id":1,"name":"ノートPC"}
type Summary = Pick
type Product = {
id: number;
name: string;
price: number;
category: string;
};
type Summary = Pick<Product, "id" | "name">;
const s: Summary = { id: 1, name: "ノートPC" };
console.log(JSON.stringify(s));
一覧表示では全項目が要らないことが多い。Pick で必要な分だけ取り出せば、余計なデータを持ち回らずに済む。
type Product = { id: number; name: string; price: number };
type NewProduct = ____<Product, "id">;
const draft: NewProduct = { name: "新商品", price: 5000 };
console.log(Object.keys(draft).join(","));
出力 == name,price
「省く」を意味する4文字のユーティリティ型。
Omit
新規作成時は id がまだ存在しない——この状況を Omit でそのまま型にできる。データベースが採番する前の状態を表現できる。
A. 文字列の配列 B. 文字列をキーに数値を持つオブジェクト C. 文字列と数値のペア D. 文字列または数値 選択★☆☆無料
解答 == B
辞書のような対応表。
B
TS11の集計で使った型。{ PC: 2, 周辺機器: 3 } のような、キーが決まっていないオブジェクトを表せる。
出力 == 1:新商品
function create(draft: NewProduct, id: number): Product { return { id, ...draft }; } と書く。
type Product = { id: number; name: string; price: number };
type NewProduct = Omit<Product, "id">;
function create(draft: NewProduct, id: number): Product {
return { id, ...draft };
}
const p = create({ name: "新商品", price: 5000 }, 1);
console.log(`${p.id}:${p.name}`);
スプレッド構文で id を足して Product に戻している。「id無しで受け取り、id付きで返す」という流れが、型として明確に表現されている。
type Config = { url: string; timeout: number };
const c: ____<Config> = { url: "https://a.com", timeout: 30 };
console.log(c.url);
出力 == https://a.com
「読み取り専用」を意味する8文字のユーティリティ型。先頭は大文字。
Readonly
全項目に readonly が付いた型になる。設定値など、途中で書き換わってほしくないオブジェクトに使う。
A. 変わらない B. 自動的に新項目も含まれる C. エラーになる D. 手動で更新が必要 選択★★☆無料
解答 == B
ユーティリティ型は元の型から自動生成される。
B
これが重複を書かない最大の利点。元の型を1箇所直すだけで、派生したすべての型に反映されるので、修正漏れが起きない。
出力 == ノートPC:150,000円
return { ...product, ...patch }; とスプレッドでマージする。
type Product = { id: number; name: string; price: number };
function update(product: Product, patch: Partial<Product>): Product {
return { ...product, ...patch };
}
const original: Product = { id: 1, name: "ノートPC", price: 128000 };
const updated = update(original, { price: 150000 });
console.log(`${updated.name}:${updated.price.toLocaleString()}円`);
これがAPIの更新処理の典型形。変更したい項目だけを送り、サーバー側で既存データとマージする——Partial がその「一部だけ」を型で表現している。
出力 == PC:5, 周辺機器:42
const stocks: Record
const stocks: Record<string, number> = {
PC: 5,
周辺機器: 42,
};
console.log(
Object.entries(stocks)
.map(([k, v]) => `${k}:${v}`)
.join(", ")
);
キーが事前に決まっていない対応表を表す型。集計結果やマスタデータの保持に向いている。
4実践シナリオ(5問)
出力2行が完全一致
Pick
type Product = {
id: number;
name: string;
price: number;
category: string;
stock: number;
};
type ListItem = Pick<Product, "id" | "name" | "price">;
const list: ListItem[] = [
{ id: 1, name: "ノートPC", price: 128000 },
{ id: 2, name: "マウス", price: 3200 },
];
for (const item of list) {
console.log(`${item.id}: ${item.name} (${item.price.toLocaleString()}円)`);
}
一覧APIは全項目を返さないことが多い。Pick で「一覧で使う分だけ」の型を作れば、存在しない項目にアクセスするミスを防げる。
出力 == ID:1 新商品 (2026-01-01)
Omit
type Product = {
id: number;
name: string;
price: number;
createdAt: string;
};
type ProductForm = Omit<Product, "id" | "createdAt">;
function register(form: ProductForm, id: number, createdAt: string): Product {
return { id, createdAt, ...form };
}
const p = register({ name: "新商品", price: 5000 }, 1, "2026-01-01");
console.log(`ID:${p.id} ${p.name} (${p.createdAt})`);
ユーザーが入力する項目と、システムが付与する項目を型で分けている。フォームに createdAt を入力させる、といった設計ミスが起こらない。
出力2行。ノートPC:150,000円 / 新ノートPC:99,000円
スプレッドでマージする。2回とも元の商品から更新する。
type Product = { id: number; name: string; price: number };
function patchProduct(p: Product, patch: Partial<Product>): Product {
return { ...p, ...patch };
}
const original: Product = { id: 1, name: "ノートPC", price: 128000 };
const a = patchProduct(original, { price: 150000 });
console.log(`${a.name}:${a.price.toLocaleString()}円`);
const b = patchProduct(original, { name: "新ノートPC", price: 99000 });
console.log(`${b.name}:${b.price.toLocaleString()}円`);
元の商品は変更されていないことに注目。スプレッドで新しいオブジェクトを作っているので、元データが壊れない。
出力 == 税率:0.1 送料:800 閾値:5000
既定値を Readonly
type Config = {
taxRate: number;
shippingFee: number;
freeThreshold: number;
};
const DEFAULT: Readonly<Config> = {
taxRate: 0.1,
shippingFee: 600,
freeThreshold: 5000,
};
function mergeConfig(override: Partial<Config>): Config {
return { ...DEFAULT, ...override };
}
const config = mergeConfig({ shippingFee: 800 });
console.log(
`税率:${config.taxRate} 送料:${config.shippingFee} 閾値:${config.freeThreshold}`
);
既定値を Readonly にすることで、誤って書き換えられない。マージは新しいオブジェクトを作るので、DEFAULT は常に元のまま保たれる。
出力 == PC:173,000円
reduce
type Product = { name: string; category: string; price: number };
const items: Product[] = [
{ name: "ノートPC", category: "PC", price: 128000 },
{ name: "マウス", category: "周辺機器", price: 3200 },
{ name: "モニター", category: "PC", price: 45000 },
];
const totals = items.reduce<Record<string, number>>((acc, i) => {
acc[i.category] = (acc[i.category] ?? 0) + i.price;
return acc;
}, {});
const large = Object.entries(totals).filter(([, v]) => v >= 100000);
console.log(
large.map(([k, v]) => `${k}:${v.toLocaleString()}円`).join(", ")
);
Object.entries の結果は [string, number][] と推論される。Record の型情報が最後まで追跡されるので、filter や map の中でも安全に扱える。
5仕上げ課題
基本の型
type Product = {
id: number;
name: string;
price: number;
category: string;
stock: number;
};
派生させる型(ユーティリティ型を使うこと)
① ProductSummary … id・name・price のみ(Pick)
② ProductInput … id を除く(Omit)
③ ProductPatch … 全項目省略可能、ただし id は除く(Partial + Omit)
実装する関数
・create(input: ProductInput, id: number): Product
・patch(product: Product, changes: ProductPatch): Product
・summarize(product: Product): ProductSummary
処理:新規作成 → 価格と在庫を更新 → 一覧用に変換
期待される出力(3行)
作成:ノートPC 128,000円 在庫5
更新:ノートPC 150,000円 在庫3
一覧:{"id":1,"name":"ノートPC","price":150000}
出力3行が完全一致
ProductPatch は Partial
type Product = {
id: number;
name: string;
price: number;
category: string;
stock: number;
};
type ProductSummary = Pick<Product, "id" | "name" | "price">;
type ProductInput = Omit<Product, "id">;
type ProductPatch = Partial<Omit<Product, "id">>;
function create(input: ProductInput, id: number): Product {
return { id, ...input };
}
function patch(product: Product, changes: ProductPatch): Product {
return { ...product, ...changes };
}
function summarize(product: Product): ProductSummary {
return {
id: product.id,
name: product.name,
price: product.price,
};
}
const created = create(
{ name: "ノートPC", price: 128000, category: "PC", stock: 5 },
1
);
console.log(
`作成:${created.name} ${created.price.toLocaleString()}円 在庫${created.stock}`
);
const updated = patch(created, { price: 150000, stock: 3 });
console.log(
`更新:${updated.name} ${updated.price.toLocaleString()}円 在庫${updated.stock}`
);
console.log(`一覧:${JSON.stringify(summarize(updated))}`);
1つの型から3つの型が派生し、それぞれ別の用途を担っている。重要なのは、Product に項目を1つ足せば、3つの派生型すべてに自動で反映されることだ。手書きで似た型を並べていたら、必ずどこかで更新漏れが起きていた。
とくに ProductPatch = Partial<Omit<Product, "id">> の入れ子に注目してほしい。「idは変更できない」「それ以外は全部省略可能」——この2つのルールが、型の合成だけで表現されている。もし patch(product, { id: 999 }) と書けば、その場でエラーになる。更新してはいけない項目を、更新できないように型で封じている。
ユーティリティ型は「便利な省略記法」に見えるが、本質はひとつの真実を1箇所に置くという設計思想にある。同じ情報を複数の場所に書けば、必ず食い違いが生まれる。派生させる仕組みがあれば、その危険を根本から断てる。
次章では非同期処理と型を学ぶ。JS15で学んだ Promise に型が付くと、await の忘れすら検出できるようになる。