開発

TypeScript ベストプラクティス 2025

cyberwolf.studio
開発/12 min read/January 15, 2025
Danylo Kabanov
Danylo Kabanov
Chief Technology Officer

#TypeScript ベストプラクティス 2025

TypeScriptは、堅牢で保守可能なJavaScriptアプリケーションを構築するための事実上の標準となっています。ここでは、2025年における最新のベストプラクティスを紹介します。

##厳格な型チェックを有効にする

tsconfig.jsonで厳格モードを有効にすることで、多くのバグを防ぐことができます。

json
1{
2 "compilerOptions": {
3 "strict": true,
4 "noUncheckedIndexedAccess": true,
5 "noImplicitOverride": true,
6 "noPropertyAccessFromIndexSignature": true,
7 "exactOptionalPropertyTypes": true
8 }
9}

##型推論を活用する

TypeScriptの型推論を信頼し、明示的な型注釈を減らします。

typescript
1// ❌ 避けるべき:冗長な型注釈
2const name: string = "CyberWolf"
3const count: number = 42
4
5// ✅ 推奨:型推論に任せる
6const name = "CyberWolf"
7const count = 42
8
9// ✅ 複雑な型には明示的に注釈を付ける
10const config: AppConfig = {
11 apiUrl: process.env.API_URL,
12 timeout: 5000,
13}

##判別可能なユニオン型

状態管理やエラーハンドリングに判別可能なユニオン型を使用します。

typescript
1type Result<T, E = Error> =
2 | { success: true; data: T }
3 | { success: false; error: E }
4
5async 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}
14
15// 使用例
16const result = await fetchUser("123")
17if (result.success) {
18 console.log(result.data.name) // 型安全
19} else {
20 console.error(result.error.message)
21}

##ジェネリクスを効果的に使用する

再利用可能で型安全なコンポーネントとユーティリティを作成します。

typescript
1// 汎用的なAPIレスポンス型
2interface ApiResponse<T> {
3 data: T
4 status: number
5 message: string
6}
7
8// 型安全なデータフェッチャー
9async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
10 const response = await fetch(url)
11 return response.json()
12}
13
14// 使用例 - 完全に型付けされている
15const userResponse = await fetchData<User>('/api/user')
16const postsResponse = await fetchData<Post[]>('/api/posts')

##ユーティリティ型を活用する

TypeScriptの組み込みユーティリティ型で型変換を簡単にします。

typescript
1interface User {
2 id: string
3 name: string
4 email: string
5 role: 'admin' | 'user'
6 createdAt: Date
7}
8
9// 一部のフィールドのみを選択
10type UserPreview = Pick<User, 'id' | 'name'>
11
12// 特定のフィールドを除外
13type UserWithoutDates = Omit<User, 'createdAt'>
14
15// すべてのフィールドをオプショナルに
16type PartialUser = Partial<User>
17
18// すべてのフィールドを必須に
19type RequiredUser = Required<Partial<User>>
20
21// すべてのフィールドを読み取り専用に
22type ImmutableUser = Readonly<User>

##カスタム型ガード

実行時の型チェックで型安全性を向上させます。

typescript
1function isError(value: unknown): value is Error {
2 return value instanceof Error
3}
4
5function isString(value: unknown): value is string {
6 return typeof value === 'string'
7}
8
9function assertNever(value: never): never {
10 throw new Error(`予期しない値: ${JSON.stringify(value)}`)
11}
12
13// 使用例
14function processValue(value: string | number | Error) {
15 if (isString(value)) {
16 return value.toUpperCase()
17 } else if (typeof value === 'number') {
18 return value * 2
19 } else if (isError(value)) {
20 throw value
21 } else {
22 assertNever(value) // すべてのケースを網羅していることを保証
23 }
24}

##型安全なイベントエミッター

イベント駆動アーキテクチャで型安全性を維持します。

typescript
1type EventMap = {
2 'user:login': { userId: string; timestamp: Date }
3 'user:logout': { userId: string }
4 'data:updated': { entityId: string; changes: Record<string, unknown> }
5}
6
7class TypedEventEmitter<T extends Record<string, any>> {
8 private listeners = new Map<keyof T, Set<(data: any) => void>>()
9
10 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 }
16
17 emit<K extends keyof T>(event: K, data: T[K]) {
18 this.listeners.get(event)?.forEach(listener => listener(data))
19 }
20}
21
22// 使用例
23const emitter = new TypedEventEmitter<EventMap>()
24
25emitter.on('user:login', (data) => {
26 console.log(`ユーザー ${data.userId} がログインしました`)
27})
28
29emitter.emit('user:login', {
30 userId: '123',
31 timestamp: new Date()
32}) // 型安全

##設定オブジェクトパターン

複雑な関数シグネチャを簡素化します。

typescript
1// ❌ 多数のパラメータ
2function createUser(
3 name: string,
4 email: string,
5 age?: number,
6 role?: string,
7 isActive?: boolean
8) { /* ... */ }
9
10// ✅ 設定オブジェクトを使用
11interface CreateUserOptions {
12 name: string
13 email: string
14 age?: number
15 role?: string
16 isActive?: boolean
17}
18
19function createUser(options: CreateUserOptions) {
20 const { name, email, age = 18, role = 'user', isActive = true } = options
21 // ...
22}
23
24// より読みやすい呼び出し
25createUser({
26 name: 'Alice',
27 email: 'alice@example.com',
28 role: 'admin',
29})

##Zodでランタイム検証

実行時の型安全性のためにZodのようなスキーマ検証ライブラリを使用します。

typescript
1import { z } from 'zod'
2
3const 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})
10
11type User = z.infer<typeof UserSchema>
12
13function validateUser(data: unknown): User {
14 return UserSchema.parse(data)
15}
16
17// 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}

##パフォーマンスのヒント

###ビルド時間の最適化

json
1{
2 "compilerOptions": {
3 "incremental": true,
4 "skipLibCheck": true,
5 "module": "esnext",
6 "moduleResolution": "bundler"
7 }
8}

###プロジェクト参照の使用

大規模なモノレポでは、プロジェクト参照を使用してビルド時間を短縮します。

json
1{
2 "references": [
3 { "path": "./packages/shared" },
4 { "path": "./packages/api" },
5 { "path": "./packages/web" }
6 ]
7}

##結論

TypeScriptのベストプラクティスに従うことで、より安全で、保守しやすく、スケーラブルなアプリケーションを構築できます。型システムを信頼し、厳格なチェックを活用し、コードベースを常に型安全に保ちましょう。

***

TypeScriptプロジェクトのコンサルティングが必要ですか?お問い合わせください。

TypeScriptベストプラクティスコード品質