create-ai-project 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents-en/investigator.md +131 -0
- package/.claude/agents-en/solver.md +145 -0
- package/.claude/agents-en/verifier.md +165 -0
- package/.claude/agents-ja/investigator.md +131 -0
- package/.claude/agents-ja/solver.md +145 -0
- package/.claude/agents-ja/verifier.md +165 -0
- package/.claude/commands-en/diagnose.md +123 -0
- package/.claude/commands-ja/diagnose.md +123 -0
- package/README.ja.md +4 -0
- package/README.md +4 -0
- package/package.json +8 -3
- package/.claude/agents/acceptance-test-generator.md +0 -250
- package/.claude/agents/code-reviewer.md +0 -187
- package/.claude/agents/design-sync.md +0 -221
- package/.claude/agents/document-reviewer.md +0 -187
- package/.claude/agents/integration-test-reviewer.md +0 -192
- package/.claude/agents/prd-creator.md +0 -190
- package/.claude/agents/quality-fixer-frontend.md +0 -324
- package/.claude/agents/quality-fixer.md +0 -281
- package/.claude/agents/requirement-analyzer.md +0 -161
- package/.claude/agents/rule-advisor.md +0 -175
- package/.claude/agents/task-decomposer.md +0 -285
- package/.claude/agents/task-executor-frontend.md +0 -264
- package/.claude/agents/task-executor.md +0 -258
- package/.claude/agents/technical-designer-frontend.md +0 -444
- package/.claude/agents/technical-designer.md +0 -368
- package/.claude/agents/work-planner.md +0 -208
- package/.claude/commands/build.md +0 -75
- package/.claude/commands/design.md +0 -37
- package/.claude/commands/front-build.md +0 -103
- package/.claude/commands/front-design.md +0 -42
- package/.claude/commands/front-plan.md +0 -40
- package/.claude/commands/implement.md +0 -73
- package/.claude/commands/plan.md +0 -43
- package/.claude/commands/project-inject.md +0 -76
- package/.claude/commands/refine-skill.md +0 -206
- package/.claude/commands/review.md +0 -78
- package/.claude/commands/sync-skills.md +0 -116
- package/.claude/commands/task.md +0 -13
- package/.claude/settings.local.json +0 -94
- package/.claude/skills/coding-standards/SKILL.md +0 -246
- package/.claude/skills/documentation-criteria/SKILL.md +0 -193
- package/.claude/skills/documentation-criteria/references/adr-template.md +0 -64
- package/.claude/skills/documentation-criteria/references/design-template.md +0 -242
- package/.claude/skills/documentation-criteria/references/plan-template.md +0 -130
- package/.claude/skills/documentation-criteria/references/prd-template.md +0 -109
- package/.claude/skills/frontend/technical-spec/SKILL.md +0 -147
- package/.claude/skills/frontend/typescript-rules/SKILL.md +0 -315
- package/.claude/skills/frontend/typescript-testing/SKILL.md +0 -212
- package/.claude/skills/implementation-approach/SKILL.md +0 -141
- package/.claude/skills/integration-e2e-testing/SKILL.md +0 -146
- package/.claude/skills/project-context/SKILL.md +0 -42
- package/.claude/skills/subagents-orchestration-guide/SKILL.md +0 -212
- package/.claude/skills/task-analyzer/SKILL.md +0 -142
- package/.claude/skills/task-analyzer/references/skills-index.yaml +0 -211
- package/.claude/skills/technical-spec/SKILL.md +0 -86
- package/.claude/skills/typescript-rules/SKILL.md +0 -121
- package/.claude/skills/typescript-testing/SKILL.md +0 -155
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# PRD: [機能名]
|
|
2
|
-
|
|
3
|
-
## 概要
|
|
4
|
-
|
|
5
|
-
### 一言まとめ
|
|
6
|
-
[この機能を一言で説明]
|
|
7
|
-
|
|
8
|
-
### 背景
|
|
9
|
-
[なぜこの機能が必要か?どのような問題を解決するか?]
|
|
10
|
-
|
|
11
|
-
## ユーザーストーリー
|
|
12
|
-
|
|
13
|
-
### 主要ユーザー
|
|
14
|
-
[主なターゲットユーザーを定義]
|
|
15
|
-
|
|
16
|
-
### ユーザーストーリー
|
|
17
|
-
```
|
|
18
|
-
[ユーザー種別]として
|
|
19
|
-
[目標/願望]をしたい
|
|
20
|
-
なぜなら[期待する価値/メリット]だから
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### ユースケース
|
|
24
|
-
1. [具体的な利用シナリオ1]
|
|
25
|
-
2. [具体的な利用シナリオ2]
|
|
26
|
-
3. [具体的な利用シナリオ3]
|
|
27
|
-
|
|
28
|
-
## 機能要件
|
|
29
|
-
|
|
30
|
-
### Must Have(MVP)
|
|
31
|
-
- [ ] 要件1: [詳細説明]
|
|
32
|
-
- AC: [受入条件 - Given/When/Then形式または測定可能な基準]
|
|
33
|
-
- [ ] 要件2: [詳細説明]
|
|
34
|
-
- AC: [受入条件]
|
|
35
|
-
- [ ] 要件3: [詳細説明]
|
|
36
|
-
- AC: [受入条件]
|
|
37
|
-
|
|
38
|
-
### Nice to Have
|
|
39
|
-
- [ ] 要件1: [詳細説明]
|
|
40
|
-
- [ ] 要件2: [詳細説明]
|
|
41
|
-
|
|
42
|
-
### スコープ外
|
|
43
|
-
- 項目1: [説明と理由]
|
|
44
|
-
- 項目2: [説明と理由]
|
|
45
|
-
|
|
46
|
-
## 非機能要件
|
|
47
|
-
|
|
48
|
-
### パフォーマンス
|
|
49
|
-
- レスポンス時間: [目標値]
|
|
50
|
-
- スループット: [目標値]
|
|
51
|
-
- 同時接続数: [目標値]
|
|
52
|
-
|
|
53
|
-
### 信頼性
|
|
54
|
-
- 可用性: [目標値]
|
|
55
|
-
- エラー率: [目標値]
|
|
56
|
-
|
|
57
|
-
### セキュリティ
|
|
58
|
-
- [セキュリティ要件の詳細]
|
|
59
|
-
|
|
60
|
-
### スケーラビリティ
|
|
61
|
-
- [将来のスケーリングに関する考慮事項]
|
|
62
|
-
|
|
63
|
-
## 成功基準
|
|
64
|
-
|
|
65
|
-
### 定量的指標
|
|
66
|
-
1. [測定可能な成功指標1]
|
|
67
|
-
2. [測定可能な成功指標2]
|
|
68
|
-
3. [測定可能な成功指標3]
|
|
69
|
-
|
|
70
|
-
### 定性的指標
|
|
71
|
-
1. [ユーザー体験の指標1]
|
|
72
|
-
2. [ユーザー体験の指標2]
|
|
73
|
-
|
|
74
|
-
## 技術的考慮事項
|
|
75
|
-
|
|
76
|
-
### 依存関係
|
|
77
|
-
- [既存システムへの依存]
|
|
78
|
-
- [外部サービスへの依存]
|
|
79
|
-
|
|
80
|
-
### 制約
|
|
81
|
-
- [技術的な制約]
|
|
82
|
-
- [リソースの制約]
|
|
83
|
-
|
|
84
|
-
### 前提条件
|
|
85
|
-
- [確認が必要な前提1]
|
|
86
|
-
- [確認が必要な前提2]
|
|
87
|
-
|
|
88
|
-
### リスクと対策
|
|
89
|
-
| リスク | 影響度 | 発生確率 | 対策 |
|
|
90
|
-
|-------|-------|---------|-----|
|
|
91
|
-
| [リスク1] | 高/中/低 | 高/中/低 | [対策] |
|
|
92
|
-
| [リスク2] | 高/中/低 | 高/中/低 | [対策] |
|
|
93
|
-
|
|
94
|
-
## 未決定事項
|
|
95
|
-
|
|
96
|
-
- [ ] [質問1]: [選択肢や影響の説明]
|
|
97
|
-
- [ ] [質問2]: [選択肢や影響の説明]
|
|
98
|
-
|
|
99
|
-
*このセクションが空になるまでユーザーと議論し、確認後に削除*
|
|
100
|
-
|
|
101
|
-
## 付録
|
|
102
|
-
|
|
103
|
-
### 参考資料
|
|
104
|
-
- [関連ドキュメント1]
|
|
105
|
-
- [関連ドキュメント2]
|
|
106
|
-
|
|
107
|
-
### 用語集
|
|
108
|
-
- **用語1**: [定義]
|
|
109
|
-
- **用語2**: [定義]
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: frontend/technical-spec
|
|
3
|
-
description: 環境変数管理、アーキテクチャ設計、ビルド・テストコマンドを含むフロントエンド技術設計ルール。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 技術設計ルール(フロントエンド)
|
|
7
|
-
|
|
8
|
-
## 技術スタックの基本方針
|
|
9
|
-
TypeScriptベースのReactアプリケーション実装。アーキテクチャパターンはプロジェクトの要件と規模に応じて選択する。
|
|
10
|
-
|
|
11
|
-
## 環境変数管理とセキュリティ
|
|
12
|
-
|
|
13
|
-
### 環境変数管理
|
|
14
|
-
- **ビルドツールの環境変数システムを使用**: `process.env`はブラウザ環境で動作しない
|
|
15
|
-
- 設定レイヤーを通じて環境変数を一元管理
|
|
16
|
-
- TypeScriptによる適切な型安全性の実装
|
|
17
|
-
- デフォルト値設定と必須チェックの適切な実装
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
// ビルドツールの環境変数(公開値のみ)
|
|
21
|
-
const config = {
|
|
22
|
-
apiUrl: import.meta.env.API_URL || 'http://localhost:3000',
|
|
23
|
-
appName: import.meta.env.APP_NAME || 'My App'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// フロントエンドでは動作しない
|
|
27
|
-
const apiUrl = process.env.API_URL // NG
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### セキュリティ(クライアントサイド制約)
|
|
31
|
-
- **重要**: すべてのフロントエンドコードは公開され、ブラウザで見える
|
|
32
|
-
- **クライアントサイドに秘密情報を保存しない**: APIキー、トークン、シークレットを環境変数に含めない
|
|
33
|
-
- `.env`ファイルをGitに含めない(バックエンドと同様)
|
|
34
|
-
- 機密情報のログ出力を禁止(パスワード、トークン、個人情報)
|
|
35
|
-
- エラーメッセージに機密情報を含めない
|
|
36
|
-
|
|
37
|
-
**秘密情報の正しい取り扱い**:
|
|
38
|
-
```typescript
|
|
39
|
-
// セキュリティリスク: APIキーがブラウザで露出
|
|
40
|
-
const apiKey = import.meta.env.VITE_API_KEY
|
|
41
|
-
const response = await fetch(`https://api.example.com/data?key=${apiKey}`)
|
|
42
|
-
|
|
43
|
-
// 正しい: バックエンドが秘密情報を管理、フロントエンドはプロキシ経由でアクセス
|
|
44
|
-
const response = await fetch('/api/data') // バックエンドがAPIキー認証を処理
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## アーキテクチャ設計
|
|
48
|
-
|
|
49
|
-
### フロントエンドアーキテクチャパターン
|
|
50
|
-
|
|
51
|
-
**Reactコンポーネントアーキテクチャ**:
|
|
52
|
-
- **Function Components**: 必須、class componentsは非推奨
|
|
53
|
-
- **Custom Hooks**: ロジック再利用と依存性注入のため
|
|
54
|
-
- **コンポーネント階層**: Atoms → Molecules → Organisms → Templates → Pages
|
|
55
|
-
- **Props-driven**: コンポーネントは必要なすべてのデータをPropsで受け取る
|
|
56
|
-
- **Co-location**: テスト、スタイル、関連ファイルをコンポーネントと同じ場所に配置
|
|
57
|
-
|
|
58
|
-
**状態管理パターン**:
|
|
59
|
-
- **Local State**: コンポーネント固有の状態には`useState`
|
|
60
|
-
- **Context API**: コンポーネントツリー全体で状態を共有(テーマ、認証等)
|
|
61
|
-
- **Custom Hooks**: 状態ロジックと副作用をカプセル化
|
|
62
|
-
- **Server State**: APIデータのキャッシュにはReact QueryまたはSWR
|
|
63
|
-
|
|
64
|
-
## データフロー統一原則
|
|
65
|
-
|
|
66
|
-
### クライアントサイドのデータフロー
|
|
67
|
-
Reactアプリケーション全体で一貫したデータフローを維持:
|
|
68
|
-
|
|
69
|
-
- **Single Source of Truth**: 各状態には1つの権威あるソースがある
|
|
70
|
-
- UI状態: コンポーネントStateまたはContext
|
|
71
|
-
- サーバーデータ: React Query/SWRでキャッシュされたAPIレスポンス
|
|
72
|
-
- フォームデータ: React Hook Formを使ったControlled Components
|
|
73
|
-
|
|
74
|
-
- **単方向フロー**: データはPropsを通じて上から下へ流れる
|
|
75
|
-
```
|
|
76
|
-
APIレスポンス → State → Props → Render → UI
|
|
77
|
-
ユーザー入力 → イベントハンドラ → State更新 → 再レンダリング
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
- **Immutable Updates**: State更新には不変パターンを使用
|
|
81
|
-
```typescript
|
|
82
|
-
// 不変なState更新
|
|
83
|
-
setUsers(prev => [...prev, newUser])
|
|
84
|
-
|
|
85
|
-
// 可変なState更新(禁止)
|
|
86
|
-
users.push(newUser)
|
|
87
|
-
setUsers(users)
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### データフローにおける型安全性
|
|
91
|
-
- **Frontend → Backend**: Props/State(型保証済み) → APIリクエスト(シリアライゼーション)
|
|
92
|
-
- **Backend → Frontend**: APIレスポンス(`unknown`) → 型ガード → State(型保証済み)
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
// 型安全なデータフロー
|
|
96
|
-
async function fetchUser(id: string): Promise<User> {
|
|
97
|
-
const response = await fetch(`/api/users/${id}`)
|
|
98
|
-
const data: unknown = await response.json()
|
|
99
|
-
|
|
100
|
-
if (!isUser(data)) {
|
|
101
|
-
throw new Error('Invalid user data')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return data // User型として保証
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## ビルドとテスト
|
|
109
|
-
package.jsonの`packageManager`フィールドに応じた実行コマンドを使用すること。
|
|
110
|
-
|
|
111
|
-
### ビルドコマンド
|
|
112
|
-
- `dev` - 開発サーバー
|
|
113
|
-
- `build` - 本番ビルド
|
|
114
|
-
- `preview` - 本番ビルドのプレビュー
|
|
115
|
-
- `type-check` - 型チェック(出力なし)
|
|
116
|
-
|
|
117
|
-
### テストコマンド
|
|
118
|
-
- `test` - テスト実行
|
|
119
|
-
- `test:coverage` - カバレッジ付きテスト実行
|
|
120
|
-
- `test:coverage:fresh` - カバレッジ付きテスト実行(キャッシュクリア)
|
|
121
|
-
- `test:safe` - 安全なテスト実行(自動クリーンアップ付き)
|
|
122
|
-
- `cleanup:processes` - Vitestプロセスのクリーンアップ
|
|
123
|
-
|
|
124
|
-
### 品質チェック要件
|
|
125
|
-
実装完了時に品質チェックは必須:
|
|
126
|
-
|
|
127
|
-
**Phase 1-3: 基本チェック**
|
|
128
|
-
- `check` - Biome(lint + format)
|
|
129
|
-
- `build` - TypeScriptビルド
|
|
130
|
-
|
|
131
|
-
**Phase 4-5: テストと最終確認**
|
|
132
|
-
- `test` - テスト実行
|
|
133
|
-
- `test:coverage:fresh` - カバレッジ測定
|
|
134
|
-
- `check:all` - 全体統合チェック
|
|
135
|
-
|
|
136
|
-
### カバレッジ要件
|
|
137
|
-
- **必須**: 単体テストのカバレッジは60%以上
|
|
138
|
-
- **コンポーネント別目標**:
|
|
139
|
-
- Atoms: 70%以上
|
|
140
|
-
- Molecules: 65%以上
|
|
141
|
-
- Organisms: 60%以上
|
|
142
|
-
- Custom Hooks: 65%以上
|
|
143
|
-
- Utils: 70%以上
|
|
144
|
-
|
|
145
|
-
### 非機能要件
|
|
146
|
-
- **ブラウザ互換性**: Chrome/Firefox/Safari/Edge(最新2バージョン)
|
|
147
|
-
- **レンダリング時間**: 主要ページで5秒以内
|
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: frontend/typescript-rules
|
|
3
|
-
description: 型安全性、コンポーネント設計、状態管理、エラーハンドリングを含むReact/TypeScriptフロントエンド開発ルール。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# TypeScript 開発ルール(フロントエンド)
|
|
7
|
-
|
|
8
|
-
## 型システム
|
|
9
|
-
|
|
10
|
-
### 型安全性の原則
|
|
11
|
-
- **strictモード必須**: tsconfig.jsonでstrict: trueを設定
|
|
12
|
-
- **any型使用禁止**: コードベースでany型を使用しない
|
|
13
|
-
- **as使用最小化**: 型キャストはやむを得ない場合のみ(理由をコメント)
|
|
14
|
-
- **unknown優先**: any型が必要な場合はunknown + 型ガード
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
// 良い: 型ガード付きのunknown
|
|
18
|
-
function processData(data: unknown): User {
|
|
19
|
-
if (!isUser(data)) throw new Error('Invalid user data')
|
|
20
|
-
return data
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 悪い: any型の使用
|
|
24
|
-
function processData(data: any): User {
|
|
25
|
-
return data as User
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### 型定義のベストプラクティス
|
|
30
|
-
|
|
31
|
-
#### オブジェクト型
|
|
32
|
-
- **interface優先**: 拡張可能なオブジェクト型にはinterfaceを使用
|
|
33
|
-
- **typeはunion/intersection用**: 複合型やユーティリティ型に使用
|
|
34
|
-
- **readonlyの活用**: 不変なプロパティにはreadonlyを明示
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
// 良い: 明確な型定義
|
|
38
|
-
interface User {
|
|
39
|
-
readonly id: string
|
|
40
|
-
name: string
|
|
41
|
-
email: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type UserWithRole = User & { role: 'admin' | 'user' }
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
#### 関数型
|
|
48
|
-
- **戻り値型を明示**: 複雑なロジックを持つ関数
|
|
49
|
-
- **ジェネリクスの活用**: 再利用可能な型安全な関数
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
// 良い: 戻り値型を明示
|
|
53
|
-
function calculateTotal(items: CartItem[]): number {
|
|
54
|
-
return items.reduce((sum, item) => sum + item.price, 0)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 良い: ジェネリクスの活用
|
|
58
|
-
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
|
|
59
|
-
// implementation
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Reactコンポーネント設計
|
|
64
|
-
|
|
65
|
-
### Function Components必須
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
// 良い: Function Component
|
|
69
|
-
const UserCard: React.FC<UserCardProps> = ({ user, onSelect }) => {
|
|
70
|
-
return (
|
|
71
|
-
<div onClick={() => onSelect(user.id)}>
|
|
72
|
-
{user.name}
|
|
73
|
-
</div>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 悪い: Class Component(非推奨)
|
|
78
|
-
class UserCard extends React.Component<UserCardProps> { }
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Props型定義
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
interface ButtonProps {
|
|
85
|
-
label: string
|
|
86
|
-
onClick: () => void
|
|
87
|
-
variant?: 'primary' | 'secondary'
|
|
88
|
-
disabled?: boolean
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const Button: React.FC<ButtonProps> = ({
|
|
92
|
-
label,
|
|
93
|
-
onClick,
|
|
94
|
-
variant = 'primary',
|
|
95
|
-
disabled = false
|
|
96
|
-
}) => {
|
|
97
|
-
// implementation
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Children Props
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
interface LayoutProps {
|
|
105
|
-
children: React.ReactNode
|
|
106
|
-
sidebar?: React.ReactNode
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const Layout: React.FC<LayoutProps> = ({ children, sidebar }) => (
|
|
110
|
-
<div>
|
|
111
|
-
<main>{children}</main>
|
|
112
|
-
{sidebar && <aside>{sidebar}</aside>}
|
|
113
|
-
</div>
|
|
114
|
-
)
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## 状態管理
|
|
118
|
-
|
|
119
|
-
### useState型定義
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
// 良い: 明示的な型
|
|
123
|
-
const [user, setUser] = useState<User | null>(null)
|
|
124
|
-
const [items, setItems] = useState<Item[]>([])
|
|
125
|
-
|
|
126
|
-
// 良い: 初期値から推論可能な場合
|
|
127
|
-
const [count, setCount] = useState(0)
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### useReducerの型安全性
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
type Action =
|
|
134
|
-
| { type: 'SET_USER'; payload: User }
|
|
135
|
-
| { type: 'CLEAR_USER' }
|
|
136
|
-
| { type: 'SET_ERROR'; payload: string }
|
|
137
|
-
|
|
138
|
-
interface State {
|
|
139
|
-
user: User | null
|
|
140
|
-
error: string | null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const reducer = (state: State, action: Action): State => {
|
|
144
|
-
switch (action.type) {
|
|
145
|
-
case 'SET_USER':
|
|
146
|
-
return { ...state, user: action.payload, error: null }
|
|
147
|
-
case 'CLEAR_USER':
|
|
148
|
-
return { ...state, user: null }
|
|
149
|
-
case 'SET_ERROR':
|
|
150
|
-
return { ...state, error: action.payload }
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### Custom Hooks
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
interface UseUserReturn {
|
|
159
|
-
user: User | null
|
|
160
|
-
loading: boolean
|
|
161
|
-
error: Error | null
|
|
162
|
-
refetch: () => Promise<void>
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function useUser(userId: string): UseUserReturn {
|
|
166
|
-
const [user, setUser] = useState<User | null>(null)
|
|
167
|
-
const [loading, setLoading] = useState(true)
|
|
168
|
-
const [error, setError] = useState<Error | null>(null)
|
|
169
|
-
|
|
170
|
-
const refetch = useCallback(async () => {
|
|
171
|
-
setLoading(true)
|
|
172
|
-
try {
|
|
173
|
-
const data = await fetchUser(userId)
|
|
174
|
-
setUser(data)
|
|
175
|
-
} catch (e) {
|
|
176
|
-
setError(e instanceof Error ? e : new Error('Unknown error'))
|
|
177
|
-
} finally {
|
|
178
|
-
setLoading(false)
|
|
179
|
-
}
|
|
180
|
-
}, [userId])
|
|
181
|
-
|
|
182
|
-
useEffect(() => { refetch() }, [refetch])
|
|
183
|
-
|
|
184
|
-
return { user, loading, error, refetch }
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## エラーハンドリング
|
|
189
|
-
|
|
190
|
-
### Error Boundary
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
interface ErrorBoundaryProps {
|
|
194
|
-
children: React.ReactNode
|
|
195
|
-
fallback: React.ReactNode
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
interface ErrorBoundaryState {
|
|
199
|
-
hasError: boolean
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
203
|
-
state = { hasError: false }
|
|
204
|
-
|
|
205
|
-
static getDerivedStateFromError(): ErrorBoundaryState {
|
|
206
|
-
return { hasError: true }
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
render() {
|
|
210
|
-
if (this.state.hasError) return this.props.fallback
|
|
211
|
-
return this.props.children
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### APIエラーハンドリング
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
interface ApiError {
|
|
220
|
-
code: string
|
|
221
|
-
message: string
|
|
222
|
-
details?: Record<string, string>
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function isApiError(error: unknown): error is ApiError {
|
|
226
|
-
return (
|
|
227
|
-
typeof error === 'object' &&
|
|
228
|
-
error !== null &&
|
|
229
|
-
'code' in error &&
|
|
230
|
-
'message' in error
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function fetchWithErrorHandling<T>(url: string): Promise<T> {
|
|
235
|
-
const response = await fetch(url)
|
|
236
|
-
|
|
237
|
-
if (!response.ok) {
|
|
238
|
-
const error: unknown = await response.json()
|
|
239
|
-
if (isApiError(error)) {
|
|
240
|
-
throw new ApiError(error.code, error.message)
|
|
241
|
-
}
|
|
242
|
-
throw new Error('Unknown API error')
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return response.json() as Promise<T>
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
## イベントハンドリング
|
|
250
|
-
|
|
251
|
-
### イベント型
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
255
|
-
e.preventDefault()
|
|
256
|
-
// handle click
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
260
|
-
const value = e.target.value
|
|
261
|
-
// handle change
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
265
|
-
e.preventDefault()
|
|
266
|
-
// handle submit
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
## コーディング規約
|
|
271
|
-
|
|
272
|
-
### 命名規則
|
|
273
|
-
- **コンポーネント**: PascalCase(例: `UserProfile`)
|
|
274
|
-
- **フック**: camelCase + use接頭辞(例: `useUserData`)
|
|
275
|
-
- **型/インターフェース**: PascalCase(例: `UserProps`)
|
|
276
|
-
- **定数**: SCREAMING_SNAKE_CASE(例: `MAX_RETRY_COUNT`)
|
|
277
|
-
- **ファイル名**: コンポーネントはPascalCase、その他はcamelCase
|
|
278
|
-
|
|
279
|
-
### インポート順序
|
|
280
|
-
1. React関連
|
|
281
|
-
2. 外部ライブラリ
|
|
282
|
-
3. 内部モジュール(絶対パス)
|
|
283
|
-
4. 内部モジュール(相対パス)
|
|
284
|
-
5. 型のみのインポート
|
|
285
|
-
6. スタイル/アセット
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
import { useState, useEffect } from 'react'
|
|
289
|
-
import { useQuery } from '@tanstack/react-query'
|
|
290
|
-
import { api } from '@/lib/api'
|
|
291
|
-
import { formatDate } from '../utils'
|
|
292
|
-
import type { User } from '@/types'
|
|
293
|
-
import styles from './Component.module.css'
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## アンチパターン
|
|
297
|
-
|
|
298
|
-
### 避けるべきパターン
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
// 悪い: Propsのスプレッド展開
|
|
302
|
-
const Button = (props: ButtonProps) => <button {...props} />
|
|
303
|
-
|
|
304
|
-
// 良い: 明示的なPropsの受け渡し
|
|
305
|
-
const Button = ({ label, onClick, disabled }: ButtonProps) => (
|
|
306
|
-
<button onClick={onClick} disabled={disabled}>{label}</button>
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
// 悪い: インラインでの複雑なロジック
|
|
310
|
-
{items.filter(i => i.active).map(i => <Item key={i.id} {...i} />)}
|
|
311
|
-
|
|
312
|
-
// 良い: 事前に変数として抽出
|
|
313
|
-
const activeItems = items.filter(item => item.active)
|
|
314
|
-
{activeItems.map(item => <Item key={item.id} item={item} />)}
|
|
315
|
-
```
|