TypeScriptのユーティリティ型 — 適切に覚えておくべきもの(およびそれ以外)
部分的、選択、省略、記録、Return Type、パラメータ、Awaited — 8つのユーティリティ型で、前後例と実際に毎週使うべきかどうかについての誠実な見解。
TypeScriptドキュメントページで20以上のユーティリティ型がアルファベット順に並べられ、それぞれ1文で定義されており、どの型を実際に覚えておくべきか、どの型がフレームワークの内部開発にしか使われないかを悩ませていることは、おそらくお分かりでしょう。ここでは、より誠実な視点を提示します。8つの型、それぞれの前後例を明確に示し、日常的に使う型と、まれに使う型を明確に分けます。
もしJSONから始めて、インターフェースをまだ書いていないなら、 JSONからTypeScriptへのコンバータ を用いて、数秒で基本的なインターフェースを作成できます——その後はここに戻って、これらのユーティリティ型を上層に追加します。
Partial — 週に1回使う。最初に学習する。
がすべてのプロパティを T オプションにします。典型的な使用例は、PATCHスタイルの更新で、変更されたフィールドのみを送信することです。
// Before: manually duplicating the interface with optional versions of everything
interface UserUpdate {
name?: string;
email?: string;
age?: number;
}
// After: Partial derives it from the source of truth
interface User {
name: string;
email: string;
age: number;
}
function updateUser(id: string, updates: Partial<User>) {
// updates.name is string | undefined — TypeScript knows
}
「Before」バージョンの問題は、を追加した場合、それもを追加する必要があるということです。 Userに UserUpdate に Partial<User>の場合、ソースインターフェースにフィールドを追加すると、更新時にそのフィールドが自動的に利用可能になります。型は常に同期します。
Required — 月に1回使うが、特定のシナリオで役立ちます。
の逆です。 Partial すべての ? を削除します。バリデーション後の使用が最も効果的です:受け取ったのは緩い型の設定オブジェクトで、すべてのフィールドが存在することを確認し、その後TypeScriptがすべてのフィールドがundefinedである可能性を示唆しないようにしたい場合です。
// Before: after validating, you still fight undefined checks everywhere
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
}
function initApp(config: Config) {
config.apiUrl?.trim(); // need optional chaining even after checking
}
// After: Required signals "this has been validated, stop warning me"
function validateAndInit(raw: Config): void {
if (!raw.apiUrl) throw new Error("apiUrl required");
if (!raw.timeout) throw new Error("timeout required");
if (!raw.retries) throw new Error("retries required");
const config = raw as Required<Config>;
config.apiUrl.trim(); // no optional chaining needed
}
誠実な注記: Required<T> は実行時バリデーションを行いません——チェックを自分で書く必要があります。ただ、TypeScriptがその下流でその型を信じるように変更します。これは「このオブジェクトはすでにバリデーション済み」という意味を型レベルで伝えるための手段です。
Pick — 週に1回使う。必要なものだけを名前で指定する。
を作成し、から名前を指定したプロパティのみを含む新しい型を作成します。 T大きなインターフェースの一部として、パブリックAPIの形状、ビュー・モデル、シリアル化されたレスポンスなどに便利です。
// Before: manually maintaining a separate interface that drifts from User
interface UserPublicProfile {
name: string;
bio: string;
avatarUrl: string;
}
// After: Pick stays in sync with the source
interface User {
id: string;
name: string;
email: string; // private — shouldn't go to clients
passwordHash: string; // definitely private
bio: string;
avatarUrl: string;
}
type UserPublicProfile = Pick<User, "name" | "bio" | "avatarUrl">;
Pickは、必要なプロパティのリストが全体のプロパティリストよりも短い場合に適したツールです。 を したい場合 excludeに
Omit — 週に1回使う。不要なものを名前で指定する。
Pickの補完型です。から名前を指定したプロパティを除き、すべてのプロパティを含む型を作成します。大きなインターフェースで、除外するフィールドが少ない場合、Omitは「除外したいフィールド」を明確に示すよりも、すべての必要なフィールドをリストアップするよりも意図を明確にします。 T PickまたはOmitが行わない1つのことは、個々のプロパティの型を変更することです。フィールドをピックする場合、そのプロパティの型を変更したいなら、マップ型を用いる必要があります。これはまったく別のツールです。
// Before: manually listing every field except the sensitive ones
// Easy to miss a new field added to User later
interface UserForClient {
id: string;
name: string;
email: string;
bio: string;
avatarUrl: string;
// passwordHash intentionally omitted — but a future dev might not know that
}
// After: Omit states the exclusion explicitly
type UserForClient = Omit<User, "passwordHash">;
// If User gains a new "address" field, UserForClient gets it automatically
Record — 週に1回使う。型付き辞書。 と を作成し、すべてのキーが型
で、すべての値が型
になります。実際のメリットは、がユニオン型の場合に現れます——TypeScriptがキーを欠落している場合にエラーを出すためです。一般的なインデックスシグネチャは不完全なオブジェクトを静黙的に受け入れますが、キーがユニオン型のRecordはそれとは異なります。 K 完全性チェックが大きなメリットです。キーが意味のあるもの、例えばロール、イベントタイプ、HTTPメソッド、ステータスコードなど、欠落がバグである場合、それらの場面で特に重要です。 VReturnType — 第三方の関数を使用する場合に週に1回使う。 K 関数の返却型を抽出します。ライブラリ関数を使用し、その出力を持つ変数を型付けしたい場合に特に有用です——返却型を別途インポートする必要がない(ライブラリが必ずしも明確にインポートしていない)場合です。
// Before: index signature with no exhaustiveness checking
const rolePermissions: { [key: string]: string[] } = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
// forgot "viewer" — TypeScript doesn't notice
};
// After: Record with a union key enforces completeness
type Role = "admin" | "editor" | "viewer";
const rolePermissions: Record<Role, string[]> = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
// TypeScript error if any Role is missing
// TypeScript error if you try to add an unknown key
};
Parameters — 偶発的に使う。主にラッパー関数に使う。
関数のパラメータ型をタプルとして抽出します。誠実な見解:週に1回使うことは、多くのラッパー関数や高階ユーティリティを書く場合に限られます。しかし、必要になったときには、他の選択肢がありません。
スプレッド+レストパターン(
// Before: manually annotating the return type — it can drift
function getCurrentUser() {
return { id: "u1", name: "Alice", roles: ["admin"] as const };
}
let cachedUser: { id: string; name: string; roles: readonly string[] };
// If getCurrentUser's return changes, this annotation doesn't update automatically
// After: ReturnType derives it and keeps in sync
type CurrentUser = ReturnType<typeof getCurrentUser>;
let cachedUser: CurrentUser;
// Especially useful with unexported library types:
import { useQuery } from "@tanstack/react-query";
type QueryResult = ReturnType<typeof useQuery>;
// No need to figure out what QueryObserverResult<...> looks like or import it
)は主な慣習です。テストのモックでも見られます:
関数のシグネチャを別途インポートせずに、完全に型付けされたモックを作成できます。
// Before: manually duplicating parameter types in the wrapper
function createEvent(
type: string,
payload: Record<string, unknown>,
timestamp: number
) { /* ... */ }
// Every time createEvent changes, this wrapper needs a manual update:
function loggedCreateEvent(
type: string,
payload: Record<string, unknown>,
timestamp: number
) {
console.log("Creating event:", type);
return createEvent(type, payload, timestamp);
}
// After: Parameters keeps the wrapper in sync automatically
function loggedCreateEvent(...args: Parameters<typeof createEvent>) {
console.log("Creating event:", args[0]);
return createEvent(...args);
}
Awaited — プロミス型を呼び出さずに解包する必要がある場合に使う。...args: Parameters<F>TypeScript 4.5(2021年11月リリース)で追加されました。再帰的に jest.fn<ReturnType<F>, Parameters<F>>() を解包して
に到達します。それ以前は、アシンク関数の解決型を取得するには、条件付き型を書く必要がありましたが、そのコードは2重包まれたプロミスに対して破綻し、差分を読むのが不快でした。
組み合わせ Promise<T> はアプリケーションコードで90%のアシンク型抽出をカバーするため、単位として覚えておくべきです。 Tチェックシート ReturnType<F> extends Promise<infer U> ? U : never ユーティリティ型
// Before: conditional type to unwrap — breaks on nested Promises
async function fetchUser(id: string) {
const res = await fetch(`/api/users/${id}`);
return res.json() as { id: string; name: string; email: string };
}
type FetchedUser =
ReturnType<typeof fetchUser> extends Promise<infer U> ? U : never;
// Works for Promise<T> but not Promise<Promise<T>>
// After: Awaited handles both cases cleanly
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;
// Awaited recursively unwraps:
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<string>>>; // string
type C = Awaited<string>; // string (no-op on non-Promise)
使用頻度 Awaited<ReturnType<typeof fn>> 主な使用ケース
すべてのプロパティがオプションになる
| PATCH/更新パラメータ | プレビューカードに表示される見出し | すべてのオプションプロパティが必須になる | バリデーション後の「完全である」というシグナル |
|---|---|---|---|
Partial<T> | 指定されたプロパティのみを保持する | 毎週 | パブリックAPIの形状、ビュー・モデル |
Required<T> | 指定されたプロパティを削除する | 月次 | センシティブまたは無関係なフィールドを削除する |
Pick<T, K> | キーが型K、値が型Vのオブジェクト | 毎週 | 型付き辞書で完全性チェックを実行 |
Omit<T, K> | 関数の返却型を抽出する | 毎週 | 週に1回(特にライブラリの使用時) |
Record<K, V> | 推定または非インポートされた返却型から変数を型付けする | 毎週 | 関数のパラメータ型をタプルとして抽出する |
ReturnType<F> | 偶発的 | ラッパー関数、型付けされたモック | プロミス型を再帰的に解包する |
Parameters<F> | アシンク関数の解決型を取得する | このリストにないもの | TypeScriptはさらに |
Awaited<T> | 、およびいくつかのテンプレートリテラル型を提供しています。これらは現実に存在し、まれに役立ちますが、ライブラリコード、ジェネリックユーティリティ、型レベルメタプログラミングにしか登場しません。典型的なアプリケーションロジックでは使われません。上記の8つを覚えていれば、実際に必要になるときにそれらを調べてください。 | このリストにないもの | すべてのユーティリティ型の基本的な原理は同じです:既存の型を変換するのではなく、それを複製するのではなく、変換を記述することです。具体的なユーティリティ型の名前は、その意図をTypeScriptに伝えるための語彙にすぎません。 |
このリストに含まれていないもの
TypeScript はまた提供されています Readonly<T>, NonNullable<T>, Extract<T,U>, Exclude<T,U>, InstanceType<C>, ConstructorParameters<C>、およびいくつかのテンプレートリテラル型。これらは現実に存在し、まれに役立つものの、ライブラリコード、ジェネリックユーティリティ、および型レベルのメタプログラミングにおいて登場するもので、一般的なアプリケーションロジックには登場しません。上記の8つを暗記しているなら、実際に必要になるときにそれらを調べてください。
すべてのタイプの基本的な原理は同じです:既存のタイプの変換を記述するのではなく、それを複製するのではなく、具体的なユーティリティタイプ名は、その意図をTypeScriptで表現するための語彙にすぎません。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
