TypeScript 上級活用ガイド
TypeScript の踏み込んだ型テクニック(conditional types / template literal types / branded types など)と、v6 → v7 のメジャーアップグレード時に当たる主要な変更点・移行ノートを 1 ページに統合。実コード付き。
TypeScript で template literal / mapped / conditional / branded を組む
TypeScript の高度な型操作 — Template Literal Types で文字列合成、Mapped Type の as でキー変換、Conditional Type の分配的挙動、再帰で Path 抽出、Brand で同型異種を区別するパターンを触れる demo で確認する実装メモ。
検証日: 2026-05-10
使用バージョン:
typescript@6.0.x対象: 入門〜中級は通っている、型レベル計算で API / form / 状態管理を絞り込みたい人
入門 + 移行記事は別途。本稿は 「型は実行時には消えるが、書き手の事故を runtime ではなく compile 時に止める」 ための高度パターン。
触って試す
// 型レベルで文字列を組み立てる
type Method = "GET" | "POST";
type Path = "/users" | "/posts";
type Endpoint = `${Method} ${Path}`;
// 推論結果:
// "GET /users" | "GET /posts" | "POST /users" | "POST /posts""GET /users" | "GET /posts" | "POST /users" | "POST /posts"
union を ${} に入れると分配的に組み合わせる。HTTP endpoint や CSS class の安全な合成に使える。
5 パターン × コード + 推論結果 + 要点を切替表示。
1. Template Literal Types
型レベルで文字列を組み立てる:
type Method = "GET" | "POST" | "PUT" | "DELETE";
type Path = "/users" | "/posts" | "/comments";
type Endpoint = `${Method} ${Path}`;
// "GET /users" | "GET /posts" | "POST /users" | ...(全 12 通り)
union を ${} に入れると 分配的に組み合わせ が生成される。
実用例:
// CSS class 名を型で縛る
type Size = "sm" | "md" | "lg";
type Color = "primary" | "secondary";
type ButtonClass = `btn-${Size}-${Color}`;
// HTTP endpoint
type ApiPath = `/api/${"users" | "posts"}/${string}`;
// イベント名
type EventName = `on${Capitalize<"click" | "hover" | "focus">}`;
// "onClick" | "onHover" | "onFocus"
Capitalize<T> / Uppercase<T> / Lowercase<T> / Uncapitalize<T> の 組み込み型変換ユーティリティ が用意されている。
2. Mapped Type + Key Remapping(as 句)
as を使うと 既存型のキー名を template literal で書き換え られる。
type User = { id: string; name: string; email: string };
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// {
// getId: () => string;
// getName: () => string;
// getEmail: () => string;
// }
ポイント:
string & KでKを string 型に narrow(Capitalizeは string が必要)as neverにすればそのキーを除外(filter としても使える)- getter / setter / event 名 prefix / API client method 自動生成 に強い
3. Conditional Type + 分配的(Distributive)
extends を使うと union を分配しながら判定 できる。
type NonEmpty<T> = T extends "" ? never : T;
type ExtractStrings<T> = T extends string ? T : never;
type A = NonEmpty<"a" | "" | "b">; // "a" | "b"
type B = ExtractStrings<string | number | boolean>; // string
T が裸の型パラメータの時 自動的に union を分配する。
分配を 無効化したい時 は tuple で囲む:
type IsString<T> = [T] extends [string] ? true : false;
type C = IsString<string | number>; // false(union 全体が string か?を判定)
type IsStringDistr<T> = T extends string ? true : false;
type D = IsStringDistr<string | number>; // true | false(member ごとに判定して union)
4. 再帰型で Path 抽出
オブジェクトの ネストしたパス を文字列リテラル union として取り出す典型パターン:
type Path<T, K extends keyof T = keyof T> = K extends string
? T[K] extends object
? `${K}` | `${K}.${Path<T[K]>}`
: `${K}`
: never;
type User = {
id: string;
profile: { name: string; address: { city: string; zip: string } };
};
type UserPath = Path<User>;
// "id" | "profile" | "profile.name" | "profile.address"
// | "profile.address.city" | "profile.address.zip"
実用例:
lodash.get(obj, path)の path 引数を型 safe に- react-hook-form の
register("profile.address.city")(内部で類似の Path 型を持つ) - i18n の翻訳キー を
t("user.profile.name")形式で型補完
5. Branded Types(同型異種を区別)
UserId も PostId も中身は string。型レベルで取り違えを禁止 したい時に brand を使う。
type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;
// runtime には何も追加しない、cast で brand を付与
const u = "550e8400-e29b-..." as UserId;
const p = "660b9500-..." as PostId;
function fetchUser(id: UserId) { /* ... */ }
fetchUser(u); // OK
fetchUser(p); // ✗ 型エラー(別の brand)
fetchUser("550"); // ✗ 型エラー(plain string)
runtime には型情報が消える ので overhead ゼロ。Zod の .brand<>() も同じ発想を schema レベルで提供。
実用箇所:
- DB の主キー(UserId / PostId / OrderId が混ざる場面)
- Money(yen / dollar が同じ number で混在)
- Sanitized vs Raw の string(XSS 対策)
6. infer で型を抽出
extends の中で infer を使うと、条件にマッチした部分の型を変数に入れて取り出せる。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Awaited<T> = T extends Promise<infer U> ? U : T;
type ArrayItem<T> = T extends Array<infer U> ? U : never;
Promise<X> を分解して X を取り出す、Array<X> から要素型を取り出す、関数の戻り値型を取り出す、など。組み込みの Awaited / ReturnType / Parameters も同じ仕組み。
応用:
// 関数の最初の引数だけ取り出す
type FirstArg<T> = T extends (a: infer A, ...rest: any[]) => any ? A : never;
// tagged template の interpolation 部分を取り出す
type Args<T> = T extends `${string}${infer U}${string}` ? U : never;
7. satisfies(型安全 + リテラル保持)
v4.9+ で導入された satisfies は、型を強制すると同時にリテラル型を保持する。
// NG: const config = {...} as Record<string, string>
// → リテラル型 "https://..." が string に広がる
// OK: satisfies で型チェックしつつ narrow を維持
const config = {
api: "https://api.example.com",
cdn: "https://cdn.example.com",
} satisfies Record<string, string>;
config.api; // 型は "https://api.example.com" のリテラル
設定オブジェクト / route 定義 / theme tokens すべて satisfies が現代的標準。
8. const 型パラメータ(v5.0+)
generic に const K extends ... を付けると、呼び出し側で as const 不要でリテラル型が保たれる:
function defineRoute<const T extends string>(path: T): { path: T } {
return { path };
}
const r = defineRoute("/users/:id");
// r.path の型は "/users/:id" リテラル(as const 不要)
ライブラリ作者向け。useRouter / Zod / tRPC のような型推論を厚くしたい API で活躍。
つまずいたポイント
- 型エラーが分かりにくい時:
type _ = Tのような中間 alias を作って各段階の推論結果を確認(IDE のホバーで見える) Path<T>を巨大 union に適用するとコンパイル爆発:type SafePath<T> = T extends Record<string, unknown> ? Path<T> : neverのように防御Capitalize<K>でKがstring | number | symbolの時にエラー:Capitalize<string & K>かCapitalize<Extract<K, string>>で string に narrowascast は危険:branded type の付与以外で多用すると runtime で爆発する- 再帰型の depth エラー:
Type instantiation is excessively deep、tail-recursive に書き換えるか、深さ制限を引き下げる
関連 Topic / 関連書籍
この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:
Effective TypeScript(第2版) : 型システムの力を最大限に引き出す83項目
急速に普及が進んでいるTypeScriptの実用書! TypeScriptの実用書。TypeScript…
TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発
新しいフロントエンドの入門書決定版! 本書はReact/Next.jsとTypeScriptを用いてWebアプリケーションを開…
かんたん TypeScript
本書は、「広く・正しく・新しく」をコンセプトにTypeScriptでプログラミングをはじめるにあたって基本的なことはすべて学習できる内容となっています。また、イラストによる図解方式で概念をやさしく解説している…
ゼロからわかる TypeScript入門
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで
TypeScriptは、JavaScriptに静的型付けの機能を加えたオープンソースのプログラミング言語です。本書では、根幹となるJavaScr…
TypeScript v6/v7 で書く時の注意点
TypeScript v6 系で安定した機能(satisfies / const generics / using / NoInfer)と、v7 で進む方向性、移行で詰まりやすい設定差分を実装観点で整理。
検証日: 2026-05-09
使用バージョン:
typescript@6.0.x(現行 stable)対象: TypeScript v5 系から v6/v7 へ移行する人、新規プロジェクトを v6 ベースで始める人
TypeScript は v5 → v6 のメジャーで内部のリファクタが大きく入り、書き味が一段変わりました。 ここでは v6 で実用的になった書き味 と、v5 から移行する時に詰まる差分 を整理します。
v5 → v6 の主な変更点(俯瞰)
1. satisfies — 「型の絞り込み」と「型の保証」を両立
v4.9 で入った satisfies 演算子は、v6 系では 設定オブジェクトのデファクト に。
// 従来: 型を強制すると、リテラル型が広がってしまう
const config: Record<string, string> = {
api: "https://api.example.com",
cdn: "https://cdn.example.com",
};
config.api; // string(リテラル型が失われる)
// v6 推奨: satisfies で型をチェックしつつ、リテラル型を保つ
const config = {
api: "https://api.example.com",
cdn: "https://cdn.example.com",
} satisfies Record<string, string>;
config.api; // "https://api.example.com" (リテラル型が保持)
API 設定 / Topic 一覧 / 環境変数の型などで多用するパターン。
satisfies / 型注釈 / as の使い分け
3 つは似て非なるもの。値を「チェックしたいか」「型を広げたい/絞りたいか」で選び分け。
| 観点 | 型注釈 : T | satisfies T | as T |
|---|---|---|---|
| 構造チェック | ✓ | ✓ | ✗(コンパイラ無視) |
| リテラル型保持 | ✗(broaden) | ✓ | ✓ |
| 型安全性 | 高 | 高 | 低(誤魔化し) |
| 推奨用途 | API 引数の値 | 設定オブジェクト全般 | 型システム外との接続 |
2. const generics — as const を関数引数で
v5 で安定した const 型パラメータ は v6 で広く使われるようになりました。
function pickKeys<const K extends string>(keys: K[]): K[] {
return keys;
}
const ks = pickKeys(["a", "b", "c"]); // (\"a\" | \"b\" | \"c\")[]
// ↑ as const 不要
Zod / tRPC / next-safe-action のような型推論重視ライブラリの内部で多用されています。
3. using / 明示的リソース管理(Stage 3 → v6 標準)
using 宣言で、スコープを抜ける時に自動で Symbol.dispose を呼ぶ リソース管理が可能。
function openFile(path: string) {
const fd = fs.openSync(path, "r");
return {
fd,
[Symbol.dispose]() { fs.closeSync(fd); },
};
}
{
using f = openFile("/tmp/data");
// f を使う処理
} // ← ここで自動的に close される
Node のファイルディスクリプタ、DB コネクション、トランザクションのスコープ管理に強い。
4. NoInfer<T> — 型推論の方向を制御
v5.4 で追加された NoInfer<T> は、generic の 「推論に使わない引数」 を明示できる。
function createState<T>(initial: T, validator: (v: NoInfer<T>) => boolean) {
// T は initial からのみ推論される。validator の引数からは推論されない
}
createState({ count: 0 }, (v) => v.count >= 0);
// ↑ NoInfer なので {count: number} に正しく型付け
ライブラリ著者向けの機能だが、フォーム validation の resolver や状態管理ライブラリで有用。
5. tsconfig の差分(v5 → v6)
lib: "esnext" の意味が変わる
v6 では ESNext の参照が更新され、新しい組み込み API(Array.fromAsync / Promise.withResolvers / 等)が含まれます。
Node 22+ でないと runtime で動かない API が型として使える ようになるので、target と lib の整合性が重要に。
moduleResolution: "bundler" がほぼ標準
v5 で bundler モードが追加され、v6 ではこれを推奨。Vite / esbuild / Bun 系のプロジェクトはほぼ全て bundler で OK。
旧 node / node16 モードは Node native CommonJS / ESM の解釈が混乱しがちなので避ける。
{
"compilerOptions": {
"target": "ES2024",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true
}
}
verbatimModuleSyntax が重要
verbatimModuleSyntax: true にすると、import type / export type を省略すると build エラーになります。
v6 系ではこれが標準的な厳しさになっており、Vite / Bun / esbuild と組み合わせる時に必須に近い。
// NG(verbatimModuleSyntax: true 下)
import { User } from "./user"; // User が type-only ならエラー
// OK
import type { User } from "./user";
import { createUser } from "./user";
6. v5 → v6 移行で詰まる箇所
tsc 互換性
isolatedDeclarationsが v5.5 で追加 → v6 でも引き続き厳しい。.d.ts自動生成系(API ライブラリ)では効くが、アプリ側ではオプションmodule: "Preserve"が v6 で追加 → trans-builder(Vite / esbuild)に渡す時の挙動が変わる場合があるtsc --watchのメモリ使用量が v5 系より増える傾向。大規模 monorepo ではincremental: trueとtsBuildInfoFileを必ず指定
依存ライブラリの型互換
- React 19 の types は v6 系で完全対応。v5.x の途中バージョンは React 19 hooks の型に追いついていない
- Zod v4 の generic 推論は v6 で IDE 速度がかなり改善
- 古い @types/ パッケージ* が v6 のバージョン上限を超えている場合がある(
tscでArgument of type ... is not assignableが出る)
tsconfig 警告
- v5.x で動いていた一部の
// @ts-expect-errorが v6 で無視されるケース(エラーが出なくなった箇所) noUncheckedIndexedAccess: trueを新規に有効にすると、配列 / オブジェクトアクセスが軒並みundefined含みになる(これが v6 系で特に影響)
7. v7 系の方向性(2026-05 時点での観察)
- 型推論の更なる速度改善(generic の deep inference を再構成)
- Node ESM ↔ CJS 境界の単純化(
module: "Preserve"の標準化方向) - decorators の更なる安定(Stage 3 → 4 想定)
- 互換性破壊は最小限という方針が続く
具体的な v7 リリース時期は流動的。ライブラリ作者は v7 リリース後 3〜6 ヶ月の追従、アプリ作者は v6 系で当面安定運用 が現実解。
評価
| 観点 | v6 系での評価 | コメント |
|---|---|---|
| 型推論速度 | ◎ | v5 系より速い、特に Zod / tRPC で顕著 |
| 書き味 | ◎ | satisfies / const / using で意図が表現しやすい |
| エコシステム整合 | ○ | React 19 / Astro 6 / Vite 7 と整合済 |
| 移行コスト | △ | tsconfig の verbatimModuleSyntax で全 import 書換えが必要な場合あり |
移行時のチェックリスト
-
target/libをES2024以上にして runtime 対応を確認 -
moduleResolution: "bundler"に切替(Vite / esbuild 系) -
verbatimModuleSyntax: trueを有効化、import type漏れを修正 -
noUncheckedIndexedAccess: trueを導入する場合は段階的(エラーが大量に出るので 1 ファイルずつ) -
@types/*パッケージを最新化(古いバージョンが警告の原因になる) -
incremental: true+tsBuildInfoFileで watch のメモリ削減 - React 19 + 各種ライブラリの最新 types に上げる
関連 Topic / 関連書籍
この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:
Effective TypeScript(第2版) : 型システムの力を最大限に引き出す83項目
急速に普及が進んでいるTypeScriptの実用書! TypeScriptの実用書。TypeScript…
TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発
新しいフロントエンドの入門書決定版! 本書はReact/Next.jsとTypeScriptを用いてWebアプリケーションを開…
かんたん TypeScript
本書は、「広く・正しく・新しく」をコンセプトにTypeScriptでプログラミングをはじめるにあたって基本的なことはすべて学習できる内容となっています。また、イラストによる図解方式で概念をやさしく解説している…
ゼロからわかる TypeScript入門
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで
TypeScriptは、JavaScriptに静的型付けの機能を加えたオープンソースのプログラミング言語です。本書では、根幹となるJavaScr…
次に読むガイド
- 2 セクション統合
Zod v4 スキーマ実装ガイド
Zod v4 でランタイム + 型レベルのバリデーションを書くパターン。基本のスキーマ定義から transform / refinement / discriminated union / メッセージ国際化までを 1 ページに統合。
- 2 セクション統合
MDX 実装テクニック
MDX でドキュメント / 技術記事を書くための実装ノート。Shiki によるコードハイライト(dual theme / 差分表示 / 行ハイライト)、MDX に外部コンポーネントを注入するパターン(components prop / scope)までを 1 ページに統合。