【入門編】Utility Typesを自作する:Type Aliasで実現する高度な型変換 – TypeScript コア・型システムの基礎解析バイブル

こんにちは!フロントエンドからバックエンドまで、TypeScriptの荒波を日々一緒に乗り越えている先輩エンジニアです。

今回は、TypeScriptの学習において誰もが一度はぶつかる壁であり、同時にここを越えれば一気に「TypeScriptの景色が変わる」という胸熱なテーマ、「Utility Types(ユーティリティ型)の自作:Type Aliasで実現する高度な型変換」についてお話しします。

`Pick` や `Omit`、あるいは `Partial` といったユーティリティ型、普段の業務でも何気なく使っていますよね。「便利だな〜」で終わらせていませんか?
実は、これらはTypeScriptの型システムが持つ「計算エンジン」を使って、あなた自身の手で簡単に再現・拡張できるんです。

ここをクリアすれば、どんなに複雑なAPIレスポンスやドメインモデルの変更にも、コンパイラが味方してくれる堅牢な型定義で立ち向かえるようになりますよ。さあ、一緒に深掘りしていきましょう!

—

1. そもそも Utility Types とは?(基礎の復習)

TypeScriptには、既存の型をもとに「一部を抜き出す」「オプショナルにする」「特定のプロパティを除外する」といった、型を加工するための便利な標準ツール(Utility Types)が用意されています。

例えば、ユーザー情報を表すこんな基本の型があるとします。

type User = {
id: number;
name: string;
email: string;
age: number;
};

この中から「名前とメールアドレスだけ」を使いたい時、あなたならどうしますか? 新しく型を作り直すのは、将来 `User` 型が変わった時に二重メンテになってしまい、バグの温床になりますよね。

そこで登場するのが `Pick` です。

// id と name だけをピックアップする
type UserSummary = Pick;
// 結果: { id: number; name: string; }

この `Pick` のような仕組みは、魔法の構文ではありません。「型エイリアス(`type`)」と「Mapped Types(マッピング型)」、そして「Conditional Types(条件付き型)」というTypeScriptの強力なプリミティブ(基本部品)を組み合わせた、いわば「型の関数」なのです。

—

2. 内部構造を暴く:`Pick` と `Omit` を自作してみよう

では、TypeScriptが標準で用意している `Pick` と同じものを、自分たちの手でゼロから作ってみましょう。型の世界での「自作自演」をすることで、コンパイラが裏側でどう動いているかが手に取るように分かりますよ。

2-1. `Pick` を自作する (`MyPick`)

`Pick` は、「型 `T` から、キー `K` に合致するプロパティだけを抜き出す」処理です。これをマッピング型を使って表現してみます。

// T というオブジェクト型から、K(Tのキーのunion)だけを抽出して新しい型を作る
type MyPick = {
[P in K]: T[P];
};

// — 使ってみる —
type User = {
id: number;
name: string;
email: string;
age: number;
};

// 自作した MyPick を使ってみる
type CustomUserSummary = MyPick;

/
【コンパイル時の型評価結果】
CustomUserSummary は以下のようになります:
{
id: number;
name: string;
}
/

ここがポイント!

  • `K extends keyof T`:ここで「`K` は必ず `T` が持つキーのどれかであること」という制約(制約付き型パラメータ)をかけています。存在しないキーを指定すると、コンパイラが即座に赤波線で教えてくれます。
  • `[P in K]`:JavaScriptの `for…in` のように、指定されたキーのunion(合集合)をループして新しいプロパティを生成しています。これをマッピング型と呼びます。

2-2. `Omit` を自作する (`MyOmit`)

次は、指定したキーを「除外」する `Omit` です。実は、標準の `Omit` は内部的に `Pick` と `Exclude` という別のユーティリティを組み合わせて作られています。

これを自作することで、型の組み合わせの妙を実感できます。

// すでに用意されている組み込み型(Exclude)を使って、指定したキー以外のキーを抜き出す
type MyOmit = MyPick>;

// — 使ってみる —
// id 以外をすべて持つ型を作る
type UserWithoutId = MyOmit;

/
【コンパイル時の型評価結果】
UserWithoutId は以下のようになります:
{
name: string;
email: string;
age: number;
}
/

「型を削る」という操作も、基本の部品(`MyPick` と `Exclude`)をパズルのように組み合わせることで、綺麗に表現できるのが分かりますよね。

—

3. 実践:プロジェクト固有の「複雑なデータ構造」を変換する

基礎が分かったところで、もう少し実務に直結する「現場でよくある課題」を解決するカスタムユーティリティ型を作ってみましょう。

シナリオ:APIのレスポンスを「すべて読み取り専用(DeepReadonly)」かつ「一部オプショナル」にしたい

フロントエンドでありがちなのが、バックエンドから受け取った巨大なJSONデータを、イミュータブル(変更不可)に扱いたい、さらに特定のフィールドだけフォームの初期値用にオプショナルにしたい、というケースです。

これを一撃で解決するカスタムユーティリティ型 `PartialBy`(特定のキーだけをオプショナルにする型)を作ってみましょう。

// 指定したキー K だけをオプショナルにして、残りはそのまま残す型
type PartialBy =
// 1. 指定されなかったキーのグループ
Omit &
// 2. 指定されたキーのグループをオプショナル(?)にしたもの
Partial>;

// — 実戦投入 —
type Article = {
id: string;
title: string;
content: string;
author: string;
createdAt: Date;
};

// 「投稿作成画面」で使う型。
// id と createdAt はバックエンド側で自動生成されるため、入力フォーム(Draft)の段階ではオプショナルにしたい!
type ArticleDraft = PartialBy;

/
【生成される ArticleDraft 型の姿】
{
id?: string | undefined; // オプショナルになった!
createdAt?: Date | undefined; // オプショナルになった!
title: string; // 必須のまま
content: string; // 必須のまま
author: string; // 必須のまま
}
/

インターセクション型(`&`)を使って、「オプショナルではない部分」と「オプショナルになった部分」を合体させています。このアプローチは、実務のフォーム管理やAPIクライアントの型定義でめちゃくちゃ重宝しますよ。

—

4. 陥りやすい文法エラーと、コンパイルの罠

カスタムユーティリティ型を書いていると、初学者が必ずハマる「エラーの罠」があります。代表的なものを2つ、事前に知っておきましょう。

罠1: `Type ‘K’ is not assignable to type ‘keyof T’`

【エラーの状況】

// ❌ やりがちなミス
type BadPick = {
[P in K]: T[P]; // エラー! Type ‘K’ is not assignable to type ‘string | number | symbol’
};

【原因と対策】
マッピング型(`[P in K]`)のループ変数 `K` は、必ず `string | number | symbol` のいずれか(つまりオブジェクトのキーになり得る型)である必要があります。
必ず `K extends keyof T` のように制約(Constraint)を明示して、コンパイラに「この `K` はオブジェクトのキーとして使えるものだけに限定されているよ」と教えてあげてください。

罠2: ユニオン型を渡したときの「分配法則(Distributive Conditional Types)」の混乱

Conditional Types(`T extends U ? X : Y`)を書く際、`T` にユニオン型(`A | B`)が渡されると、TypeScriptは自動的に「`A` の場合」「`B` の場合」を個別に計算して結果を結合します(これを分配法則と呼びます)。
意図しないところでこれが起きて型がバグることがあるため、分配を抑止したい時は `[T] extends [U]` のようにタプルで囲むテクニックが必要になることがあります。少し応用ですが、頭の片隅に置いておいてくださいね。

—

まとめ

今回は、Utility Typesの裏側にある仕組みを紐解き、Type Aliasを使った高度な型変換の世界を覗いてみました。

  • Utility Typesは特別なキーワードではなく、型引数・マッピング型・条件付き型を組み合わせた「型の関数」である
  • `Pick` や `Omit`、さらに独自のビジネスロジックに合わせたカスタム型(`PartialBy` など)は、自分で自由に構築できる
  • `extends` を使った型制約を正しく書くことで、安全でメンテナンス性の高いコードベースが手に入る

ここをクリアできれば、あなたのTypeScriptスキルはもう初級者を卒業し、アーキテクトの領域へと確実に踏み込んでいます。複雑な型定義に出会っても、「恐れずに分解して自作してみる」というアプローチで、スルスルと読み解いていけるはずです。

日々のコーディングが、もっともっと楽しくなりますように。それではまた次の記事でお会いしましょう!

タイトルとURLをコピーしました