TypeScriptのOmit・Pick・Recordの使い分けとUnion型破壊事故の対策

TypeScript Frontend TypeSystem
結論

基本抽出は Pick/Omit を使い、ユニオン型には DistributiveOmit で型の崩壊を防ぎます。

// TypeScript公式仕様:ユーティリティ型の基本定義
type User = { id: string; name: string; email: string; role: 'admin' | 'user' };

type UserPreview = Pick<User, 'id' | 'name'>;           // id, name のみ抽出
type UserWithoutId = Omit<User, 'id'>;                 // id を除外
type UserRoleMap = Record<User['role'], boolean>;      // { admin: boolean; user: boolean }

ユーティリティ型3種の比較

型名機能構文例主な用途
Pick<T, K>Tから指定キー K のみ抽出Pick<User, 'id' | 'name'>コンポーネントに渡す最小プロパティの定義
Omit<T, K>Tから指定キー K を除外Omit<User, 'id'>ID自動採番時の新規作成フォーム型
Record<K, T>Kをキーとし Tを値とするマップを作成Record<string, User>IDキー指定の検索テーブルや辞書オブジェクト

実際に起こる型破壊事故:Union型への Omit 適用パターン

TypeScriptの Omit は標準で分配(Distributive)処理を行いません。そのため、識別子付きユニオン型(Discriminated Union)に対して Omit を適用すると、ユニオン共通のプロパティ以外が消失して型が壊れる事故が発生します。

type Event = 
  | { type: 'click'; x: number; y: number; timestamp: number }
  | { type: 'hover'; elementId: string; timestamp: number };

// ❌ 事故:timestamp を除外しようとすると、x, y, elementId が消滅する
type BrokenEvent = Omit<Event, 'timestamp'>; 
// 結果: { type: 'click' | 'hover' } 型に縮小されてしまう

トラブル回避策:Distributive Omit の実装手順

  1. 条件付き型(Conditional Type T extends any)を利用した分配型ヘルパーを定義する
  2. ユニオン型の各メンバーごとに Omit を個別適用させる
  3. 固有プロパティ(x, y, elementId)が保持された安全な型を作成する
// ⭕ 正解:分配条件型(Distributive Conditional Types)を適用したOmit
type DistributiveOmit<T, K extends keyof any> = T extends any 
  ? Omit<T, K> 
  : never;

// ⭕ 型が壊れず固有プロパティが維持される
type SafeEvent = DistributiveOmit<Event, 'timestamp'>;
// 結果: 
// | { type: 'click'; x: number; y: number }
// | { type: 'hover'; elementId: string }