|4 min read
実務で役立つTypeScriptパターン5選
普段の開発で実際に使っているTypeScriptの型テクニックやパターンを紹介。Discriminated Union、Branded Type、satisfies演算子など。
TypeScript
Tips
はじめに
TypeScript を書いていると「もっと型で守れないかな」と思うことがよくあります。この記事では、実務で実際に使っているパターンを5つ紹介します。
1. Discriminated Union で状態を安全に管理する
API レスポンスやUIの状態管理で特に使えるパターンです。
type ApiResult<T> =
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string }
function handleResult(result: ApiResult<User>) {
switch (result.status) {
case 'loading':
return <Spinner />
case 'success':
// result.data に安全にアクセスできる
return <UserCard user={result.data} />
case 'error':
// result.error に安全にアクセスできる
return <ErrorMessage message={result.error} />
}
}
status をキーにした分岐で、TypeScript が自動的に型を絞り込んでくれます。if-else でフラグを使うよりずっと安全。
2. Branded Type で ID の取り違えを防ぐ
string 型の ID を複数扱うとき、取り違えが起きがちです。
type UserId = string & { readonly __brand: 'UserId' }
type TeamId = string & { readonly __brand: 'TeamId' }
const createUserId = (id: string): UserId => id as UserId
const createTeamId = (id: string): TeamId => id as TeamId
function getUser(id: UserId): Promise<User> { /* ... */ }
const userId = createUserId('user-123')
const teamId = createTeamId('team-456')
getUser(userId) // OK
getUser(teamId) // コンパイルエラー!
ランタイムのコストはゼロ。型チェックだけで取り違えを防げます。
3. satisfies 演算子で型推論を維持する
as const と組み合わせると、リテラル型を維持しつつ型チェックもできます。
type Route = {
path: string
label: string
}
// ❌ as const だけだと Route 型のチェックがない
const routes1 = [
{ path: '/home', label: 'Home' },
{ path: '/about', label: 'About' },
] as const
// ✅ satisfies で型チェック + リテラル型も維持
const routes = [
{ path: '/home', label: 'Home' },
{ path: '/about', label: 'About' },
] as const satisfies readonly Route[]
// routes[0].path は "/home" リテラル型
satisfies は TypeScript 4.9 以降で使えます。古いプロジェクトでは注意。
4. Template Literal Types でパターンマッチ
イベント名やAPI パスなど、特定のパターンに従う文字列を型で縛れます。
type EventName = `on${Capitalize<string>}`
function addEventListener(event: EventName, handler: () => void) {
// ...
}
addEventListener('onClick', handleClick) // OK
addEventListener('click', handleClick) // エラー!"on" で始まらない
API のエンドポイントにも使えます。
type ApiPath = `/api/${string}`
async function fetchApi(path: ApiPath): Promise<Response> {
return fetch(path)
}
fetchApi('/api/users') // OK
fetchApi('/users') // エラー!
5. Conditional Types でユーティリティを作る
既存の型から新しい型を導出するのに使います。
// ネストしたオブジェクトを全て Optional にする
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T
type Config = {
database: {
host: string
port: number
credentials: {
username: string
password: string
}
}
}
// 全てのプロパティが Optional になる
type PartialConfig = DeepPartial<Config>
実務では設定ファイルのマージやフォーム初期値で重宝します。
まとめ
| パターン | ユースケース |
|---|---|
| Discriminated Union | 状態管理、API レスポンス |
| Branded Type | ID の取り違え防止 |
| satisfies | 型チェック + 型推論の維持 |
| Template Literal Types | パターンに従う文字列の型安全 |
| Conditional Types | ユーティリティ型の作成 |
TypeScript の型システムは奥が深いですが、これらのパターンを知っておくだけでコードの安全性がかなり上がります。
次回は AI 系のツール開発について書く予定です。