TypeScript / LESSON 13 / TS13

TypeScript:ユーティリティ型

1学習の目的

2基礎解説

「更新用に全項目を省略可能にした型」「一覧表示用に一部だけ取り出した型」——元の型があるのに、似た型を何度も書くのは無駄です。TypeScriptには変換用の型が最初から用意されています。

効果用途
Partial<T>全項目を省略可能に更新処理
Pick<T, K>指定した項目だけ一覧表示
Omit<T, K>指定した項目を除く新規作成(id無し)
Record<K, V>キーと値の対応表集計結果
✅ 覚えるべき重要ポイント
  1. Partial<T> は全項目を ? 付きにする。「変更したい項目だけを渡す」更新処理で使う。
  2. Pick<T, "a" | "b"> は必要な項目だけを抜き出す。一覧表示など、全項目が要らない場面で使う。
  3. Omit<T, "id"> は指定した項目を除く。「新規作成時はidがまだ無い」という状況をそのまま型にできる。
  4. Record<string, number> はキーと値の対応表。TS10・TS11の集計で使った型で、オブジェクトを辞書として使うときに書く。
  5. 元の型を変えれば、派生した型すべてに反映される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問)

TS13-D01 Product 型(id, name, price)を定義し、Partial を使って price だけを持つ更新用オブジェクトを作り、出力せよ。 コーディング★☆☆無料
期待される結果

出力 == {"price":1000}

ヒント

const patch: Partial = { price: 1000 }; と書く。出力は JSON.stringify を使う。

模範解答
type Product = { id: number; name: string; price: number };

const patch: Partial<Product> = { price: 1000 };

console.log(JSON.stringify(patch));
解説

Partial は全項目を省略可能にする。更新処理では「変更したい項目だけ渡す」ことが多いので、この型が便利になる。

TS13-D02 Partial<T> は何をする型か。
A. 一部の項目を取り出す B. 全項目を省略可能にする C. 全項目を必須にする D. 変更不可にする
選択★☆☆無料
期待される結果

解答 == B

ヒント

Partial は「部分的」という意味。

模範解答
B
解説

すべての項目に ? が付いた型になる。TS08で1つずつ ? を書いていた作業を、まとめて行える。

TS13-D03 Product 型(id, name, price, category)から、Pick で id と name だけの型を作り、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 で必要な分だけ取り出せば、余計なデータを持ち回らずに済む。

TS13-D04 id を除いた型を作るコードを完成させよ。 穴埋め★☆☆無料
コード
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 でそのまま型にできる。データベースが採番する前の状態を表現できる。

TS13-D05 Record<string, number> はどんな型か。
A. 文字列の配列 B. 文字列をキーに数値を持つオブジェクト C. 文字列と数値のペア D. 文字列または数値
選択★☆☆無料
期待される結果

解答 == B

ヒント

辞書のような対応表。

模範解答
B
解説

TS11の集計で使った型{ PC: 2, 周辺機器: 3 } のような、キーが決まっていないオブジェクトを表せる。

TS13-D06 Product 型から Omit で id を除いた NewProduct 型を作り、それを受け取って id を付けて返す関数 create を定義せよ。「1:新商品」と出力すること。 コーディング★★☆無料
期待される結果

出力 == 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付きで返す」という流れが、型として明確に表現されている。

TS13-D07 全項目を変更不可にする型を補ってコードを完成させよ。 穴埋め★★☆無料
コード
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 が付いた型になる。設定値など、途中で書き換わってほしくないオブジェクトに使う。

TS13-D08 元の型 Product に新しい項目を追加すると、Partial<Product> はどうなるか。
A. 変わらない B. 自動的に新項目も含まれる C. エラーになる D. 手動で更新が必要
選択★★☆無料
期待される結果

解答 == B

ヒント

ユーティリティ型は元の型から自動生成される。

模範解答
B
解説

これが重複を書かない最大の利点。元の型を1箇所直すだけで、派生したすべての型に反映されるので、修正漏れが起きない。

TS13-D09 商品を部分更新する関数 update を定義せよ(元の商品と Partial の変更内容を受け取り、マージした結果を返す)。価格だけを変更して「ノートPC:150,000円」と出力すること。 コーディング★★☆無料
期待される結果

出力 == ノート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 がその「一部だけ」を型で表現している。

TS13-D10 Record を使ってカテゴリ別の在庫数を表す型を定義し、「PC:5, 周辺機器:42」の形式で出力せよ。 コーディング★★☆無料
期待される結果

出力 == PC:5, 周辺機器:42

ヒント

const stocks: Record = { PC: 5, 周辺機器: 42 }; と書く。

模範解答
const stocks: Record<string, number> = {
  PC: 5,
  周辺機器: 42,
};

console.log(
  Object.entries(stocks)
    .map(([k, v]) => `${k}:${v}`)
    .join(", ")
);
解説

キーが事前に決まっていない対応表を表す型。集計結果やマスタデータの保持に向いている。

4実践シナリオ(5問)

TS13-S01 【一覧用の型】Product 型(id, name, price, category, stock)から Pick で id・name・price だけの ListItem 型を作り、2件を「1: ノートPC (128,000円)」の形式で2行出力せよ。 コーディング★★☆無料
期待される結果

出力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 で「一覧で使う分だけ」の型を作れば、存在しない項目にアクセスするミスを防げる。

TS13-S02 【新規登録フォーム】Product 型から Omit で id と createdAt を除いた ProductForm 型を作り、入力内容を受け取ってIDと作成日を付与する関数 register を定義せよ。「ID:1 新商品 (2026-01-01)」と出力すること。 コーディング★★☆無料
期待される結果

出力 == ID:1 新商品 (2026-01-01)

ヒント

Omit と書く。register で id と createdAt を追加する。

模範解答
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 を入力させる、といった設計ミスが起こらない。

TS13-S03 【部分更新の実装】商品を Partial で部分更新する関数 patchProduct を定義し、①価格のみ変更 ②名前と価格を変更 の2通りを試して2行出力せよ(元:ノートPC/128000)。 コーディング★★☆無料
期待される結果

出力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()}円`);
解説

元の商品は変更されていないことに注目。スプレッドで新しいオブジェクトを作っているので、元データが壊れない。

TS13-S04 【設定のマージ】既定の設定(税率0.1、送料600、閾値5000)を Readonly で定義し、Partial の上書き設定とマージする関数 mergeConfig を作れ。送料だけ800に変更した結果を「税率:0.1 送料:800 閾値:5000」の形式で出力すること。 コーディング★★★無料
期待される結果

出力 == 税率:0.1 送料:800 閾値:5000

ヒント

既定値を Readonly で定義し、Partial を受け取ってスプレッドでマージする。

模範解答
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 は常に元のまま保たれる。

TS13-S05 【集計結果の型】商品配列からカテゴリ別の合計金額を Record で集計し、さらに合計が10万円以上のカテゴリだけを抽出して「PC:173,000円」と出力せよ(ノートPC/PC/128000、マウス/周辺機器/3200、モニター/PC/45000)。 コーディング★★★無料
期待される結果

出力 == PC:173,000円

ヒント

reduce> で集計し、Object.entries で絞り込む。

模範解答
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仕上げ課題

TS13-FINAL 【商品管理APIの型設計】1つの Product 型から、用途別の型を派生させて実装せよ。

基本の型
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> のように組み合わせる。summarize は必要な3項目だけを取り出して返す。

模範解答
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 の忘れすら検出できるようになる。