#TypeScript ベストプラクティス 2025
TypeScriptは、堅牢で保守可能なJavaScriptアプリケーションを構築するための事実上の標準となっています。ここでは、2025年における最新のベストプラクティスを紹介します。
##厳格な型チェックを有効にする
tsconfig.jsonで厳格モードを有効にすることで、多くのバグを防ぐことができます。
1{2 "compilerOptions": {3 "strict": true,4 "noUncheckedIndexedAccess": true,5 "noImplicitOverride": true,6 "noPropertyAccessFromIndexSignature": true,7 "exactOptionalPropertyTypes": true8 }9}
##型推論を活用する
TypeScriptの型推論を信頼し、明示的な型注釈を減らします。
1// ❌ 避けるべき:冗長な型注釈2const name: string = "CyberWolf"3const count: number = 4245// ✅ 推奨:型推論に任せる6const name = "CyberWolf"7const count = 4289// ✅ 複雑な型には明示的に注釈を付ける10const config: AppConfig = {11 apiUrl: process.env.API_URL,12 timeout: 5000,13}
##判別可能なユニオン型
状態管理やエラーハンドリングに判別可能なユニオン型を使用します。
1type Result<T, E = Error> =2 | { success: true; data: T }3 | { success: false; error: E }45async function fetchUser(id: string): Promise<Result<User>> {6 try {7 const response = await fetch(`/api/users/${id}`)8 const data = await response.json()9 return { success: true, data }10 } catch (error) {11 return { success: false, error: error as Error }12 }13}1415// 使用例16const result = await fetchUser("123")17if (result.success) {18 console.log(result.data.name) // 型安全19} else {20 console.error(result.error.message)21}
##ジェネリクスを効果的に使用する
再利用可能で型安全なコンポーネントとユーティリティを作成します。
1// 汎用的なAPIレスポンス型2interface ApiResponse<T> {3 data: T4 status: number5 message: string6}78// 型安全なデータフェッチャー9async function fetchData<T>(url: string): Promise<ApiResponse<T>> {10 const response = await fetch(url)11 return response.json()12}1314// 使用例 - 完全に型付けされている15const userResponse = await fetchData<User>('/api/user')16const postsResponse = await fetchData<Post[]>('/api/posts')
##ユーティリティ型を活用する
TypeScriptの組み込みユーティリティ型で型変換を簡単にします。
1interface User {2 id: string3 name: string4 email: string5 role: 'admin' | 'user'6 createdAt: Date7}89// 一部のフィールドのみを選択10type UserPreview = Pick<User, 'id' | 'name'>1112// 特定のフィールドを除外13type UserWithoutDates = Omit<User, 'createdAt'>1415// すべてのフィールドをオプショナルに16type PartialUser = Partial<User>1718// すべてのフィールドを必須に19type RequiredUser = Required<Partial<User>>2021// すべてのフィールドを読み取り専用に22type ImmutableUser = Readonly<User>
##カスタム型ガード
実行時の型チェックで型安全性を向上させます。
1function isError(value: unknown): value is Error {2 return value instanceof Error3}45function isString(value: unknown): value is string {6 return typeof value === 'string'7}89function assertNever(value: never): never {10 throw new Error(`予期しない値: ${JSON.stringify(value)}`)11}1213// 使用例14function processValue(value: string | number | Error) {15 if (isString(value)) {16 return value.toUpperCase()17 } else if (typeof value === 'number') {18 return value * 219 } else if (isError(value)) {20 throw value21 } else {22 assertNever(value) // すべてのケースを網羅していることを保証23 }24}
##型安全なイベントエミッター
イベント駆動アーキテクチャで型安全性を維持します。
1type EventMap = {2 'user:login': { userId: string; timestamp: Date }3 'user:logout': { userId: string }4 'data:updated': { entityId: string; changes: Record<string, unknown> }5}67class TypedEventEmitter<T extends Record<string, any>> {8 private listeners = new Map<keyof T, Set<(data: any) => void>>()910 on<K extends keyof T>(event: K, listener: (data: T[K]) => void) {11 if (!this.listeners.has(event)) {12 this.listeners.set(event, new Set())13 }14 this.listeners.get(event)!.add(listener)15 }1617 emit<K extends keyof T>(event: K, data: T[K]) {18 this.listeners.get(event)?.forEach(listener => listener(data))19 }20}2122// 使用例23const emitter = new TypedEventEmitter<EventMap>()2425emitter.on('user:login', (data) => {26 console.log(`ユーザー ${data.userId} がログインしました`)27})2829emitter.emit('user:login', {30 userId: '123',31 timestamp: new Date()32}) // 型安全
##設定オブジェクトパターン
複雑な関数シグネチャを簡素化します。
1// ❌ 多数のパラメータ2function createUser(3 name: string,4 email: string,5 age?: number,6 role?: string,7 isActive?: boolean8) { /* ... */ }910// ✅ 設定オブジェクトを使用11interface CreateUserOptions {12 name: string13 email: string14 age?: number15 role?: string16 isActive?: boolean17}1819function createUser(options: CreateUserOptions) {20 const { name, email, age = 18, role = 'user', isActive = true } = options21 // ...22}2324// より読みやすい呼び出し25createUser({26 name: 'Alice',27 email: 'alice@example.com',28 role: 'admin',29})
##Zodでランタイム検証
実行時の型安全性のためにZodのようなスキーマ検証ライブラリを使用します。
1import { z } from 'zod'23const UserSchema = z.object({4 id: z.string().uuid(),5 name: z.string().min(1).max(100),6 email: z.string().email(),7 age: z.number().int().min(0).max(150),8 role: z.enum(['admin', 'user', 'guest']),9})1011type User = z.infer<typeof UserSchema>1213function validateUser(data: unknown): User {14 return UserSchema.parse(data)15}1617// APIレスポンスの検証18async function fetchUser(id: string): Promise<User> {19 const response = await fetch(`/api/users/${id}`)20 const data = await response.json()21 return validateUser(data) // ランタイムで検証22}
##パフォーマンスのヒント
###ビルド時間の最適化
1{2 "compilerOptions": {3 "incremental": true,4 "skipLibCheck": true,5 "module": "esnext",6 "moduleResolution": "bundler"7 }8}
###プロジェクト参照の使用
大規模なモノレポでは、プロジェクト参照を使用してビルド時間を短縮します。
1{2 "references": [3 { "path": "./packages/shared" },4 { "path": "./packages/api" },5 { "path": "./packages/web" }6 ]7}
##結論
TypeScriptのベストプラクティスに従うことで、より安全で、保守しやすく、スケーラブルなアプリケーションを構築できます。型システムを信頼し、厳格なチェックを活用し、コードベースを常に型安全に保ちましょう。
TypeScriptプロジェクトのコンサルティングが必要ですか?お問い合わせください。