create-ai-project 1.23.2 → 1.23.4
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/acceptance-test-generator.md +4 -2
- package/.claude/agents-en/code-reviewer.md +3 -0
- package/.claude/agents-en/quality-fixer-frontend.md +3 -3
- package/.claude/agents-en/quality-fixer.md +3 -3
- package/.claude/agents-en/task-decomposer.md +2 -1
- package/.claude/agents-en/task-executor-frontend.md +8 -2
- package/.claude/agents-en/task-executor.md +8 -2
- package/.claude/agents-en/technical-designer-frontend.md +10 -48
- package/.claude/agents-en/technical-designer.md +10 -26
- package/.claude/agents-en/work-planner.md +2 -5
- package/.claude/agents-ja/acceptance-test-generator.md +4 -2
- package/.claude/agents-ja/code-reviewer.md +3 -0
- package/.claude/agents-ja/quality-fixer-frontend.md +3 -3
- package/.claude/agents-ja/quality-fixer.md +3 -3
- package/.claude/agents-ja/task-decomposer.md +2 -1
- package/.claude/agents-ja/task-executor-frontend.md +8 -2
- package/.claude/agents-ja/task-executor.md +8 -2
- package/.claude/agents-ja/technical-designer-frontend.md +10 -48
- package/.claude/agents-ja/technical-designer.md +9 -25
- package/.claude/agents-ja/work-planner.md +2 -5
- package/.claude/commands-en/build.md +6 -15
- package/.claude/commands-en/front-build.md +4 -13
- package/.claude/commands-en/implement.md +2 -15
- package/.claude/commands-en/plan.md +7 -2
- package/.claude/commands-en/prepare-implementation.md +7 -17
- package/.claude/commands-en/sync-skills.md +3 -3
- package/.claude/commands-ja/build.md +7 -16
- package/.claude/commands-ja/front-build.md +4 -13
- package/.claude/commands-ja/implement.md +2 -15
- package/.claude/commands-ja/plan.md +6 -1
- package/.claude/commands-ja/prepare-implementation.md +8 -18
- package/.claude/commands-ja/sync-skills.md +3 -3
- package/.claude/skills-en/documentation-criteria/references/plan-template.md +0 -2
- package/.claude/skills-en/frontend-technical-spec/SKILL.md +4 -4
- package/.claude/skills-en/frontend-typescript-rules/SKILL.md +45 -111
- package/.claude/skills-en/frontend-typescript-testing/SKILL.md +8 -6
- package/.claude/skills-en/integration-e2e-testing/SKILL.md +2 -0
- package/.claude/skills-en/subagents-orchestration-guide/SKILL.md +2 -7
- package/.claude/skills-en/task-analyzer/references/skills-index.yaml +9 -11
- package/.claude/skills-ja/documentation-criteria/references/plan-template.md +0 -2
- package/.claude/skills-ja/frontend-technical-spec/SKILL.md +3 -3
- package/.claude/skills-ja/frontend-typescript-rules/SKILL.md +43 -288
- package/.claude/skills-ja/frontend-typescript-testing/SKILL.md +15 -71
- package/.claude/skills-ja/integration-e2e-testing/SKILL.md +2 -0
- package/.claude/skills-ja/subagents-orchestration-guide/SKILL.md +2 -7
- package/.claude/skills-ja/task-analyzer/references/skills-index.yaml +10 -11
- package/CHANGELOG.md +19 -0
- package/package.json +1 -1
|
@@ -5,132 +5,66 @@ description: Applies React/TypeScript type safety, component design, and state m
|
|
|
5
5
|
|
|
6
6
|
# TypeScript Development Rules (Frontend)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Frontend-specific React/TypeScript rules for implementation: thresholds, boundary type safety, component/state design, error handling, and project conventions.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Anti-patterns and Thresholds
|
|
11
|
+
Signals that trigger a design change:
|
|
12
|
+
- Prop drilling through 3+ levels → lift to Context or state management
|
|
13
|
+
- Component over 300 lines → split
|
|
14
|
+
- Props count over 10 → split the component (3-7 is the working range)
|
|
15
|
+
- Optional props over 50% → introduce defaults or Context
|
|
16
|
+
- Props nesting deeper than 2 levels → flatten
|
|
17
|
+
- The same `as` assertion appearing 3+ times → revisit the type design
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
## Type Safety at Boundaries
|
|
20
|
+
Prohibit `any`; when a type is unavailable, receive it as `unknown` and narrow with a type guard. Minimize `as` (justify with a comment when unavoidable).
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
Inside the app, React Props/State are type-guaranteed — no `unknown` needed. At every external boundary, receive as `unknown` and narrow with a type guard before use: API responses, `localStorage`/`sessionStorage`, URL parameters, parsed JSON. Controlled-component form input stays type-safe through React synthetic events.
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **React Props/State**: TypeScript manages types, unknown unnecessary
|
|
23
|
-
- **External API Responses**: Always receive as `unknown`, validate with type guards
|
|
24
|
-
- **localStorage/sessionStorage**: Treat as `unknown`, validate
|
|
25
|
-
- **URL Parameters**: Treat as `unknown`, validate
|
|
26
|
-
- **Form Input (Controlled Components)**: Type-safe with React synthetic events
|
|
27
|
-
|
|
28
|
-
**Type Complexity Management (Frontend)**
|
|
29
|
-
- **Props Design**:
|
|
30
|
-
- Props count: 3-7 props ideal (consider component splitting if exceeds 10)
|
|
31
|
-
- Optional Props: 50% or less (consider default values or Context if excessive)
|
|
32
|
-
- Nesting: Up to 2 levels (flatten deeper structures)
|
|
33
|
-
- Type Assertions: Review design if used 3+ times
|
|
34
|
-
- **External API Types**: Relax constraints and define according to reality (convert appropriately internally)
|
|
35
|
-
|
|
36
|
-
## Coding Conventions
|
|
37
|
-
|
|
38
|
-
**Component Design Criteria**
|
|
39
|
-
- **Function Components (Mandatory)**: Official React recommendation, optimizable by modern tooling
|
|
40
|
-
- **Classes Prohibited**: Class components completely deprecated (Exception: Error Boundary)
|
|
41
|
-
- **Custom Hooks**: Standard pattern for logic reuse
|
|
42
|
-
|
|
43
|
-
**Function Design**
|
|
44
|
-
- **0-2 parameters maximum**: Use object for 3+ parameters
|
|
45
|
-
```typescript
|
|
46
|
-
// Object parameter
|
|
47
|
-
function createUser({ name, email, role }: CreateUserParams) {}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Props Design (Props-driven Approach)**
|
|
51
|
-
- Props are the interface: Define all necessary information as props
|
|
52
|
-
- Avoid implicit dependencies: Do not depend on global state or context without necessity
|
|
53
|
-
- Type-safe: Always define Props type explicitly
|
|
54
|
-
|
|
55
|
-
**Environment Variables**
|
|
56
|
-
- **Use build tool's environment variable system**: `process.env` does not work in browsers
|
|
57
|
-
- **No secrets on client-side**: All frontend code is public, manage secrets in backend
|
|
58
|
-
|
|
59
|
-
**Dependency Injection**
|
|
60
|
-
- **Custom Hooks for dependency injection**: Ensure testability and modularity
|
|
61
|
-
|
|
62
|
-
**Asynchronous Processing**
|
|
63
|
-
- Promise Handling: Always use `async/await`
|
|
64
|
-
- Error Handling: Always handle with `try-catch` or Error Boundary
|
|
65
|
-
- Type Definition: Explicitly define return value types (e.g., `Promise<Result>`)
|
|
66
|
-
|
|
67
|
-
**Format Rules**
|
|
68
|
-
- Semicolon omission (follow Biome settings)
|
|
69
|
-
- Types in `PascalCase`, variables/functions in `camelCase`
|
|
70
|
-
- Imports use absolute paths (`src/`)
|
|
24
|
+
```typescript
|
|
25
|
+
const raw: unknown = await (await fetch(url)).json()
|
|
26
|
+
if (!isUser(raw)) throw new ValidationError('invalid user')
|
|
27
|
+
const user = raw // narrowed to User
|
|
28
|
+
```
|
|
71
29
|
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
30
|
+
## Component and State Design
|
|
31
|
+
- **Function components only.** Class components are allowed solely for Error Boundaries (no hook equivalent exists).
|
|
32
|
+
- **Type Props explicitly** with a named type and destructure: `function UserCard({ user, onSelect }: UserCardProps)`. Avoid `React.FC`; type props directly on the function so the props contract stays explicit.
|
|
33
|
+
- **Props-driven:** pass dependencies as props; reach into global state or Context only when needed.
|
|
34
|
+
- **Custom hooks** are the unit of logic reuse and dependency injection (inject collaborators through the hook for testability).
|
|
35
|
+
- **Function parameters:** 0-2 positional; for 3+ take a single options object.
|
|
36
|
+
- **State shape:** type state explicitly; for multi-field state with discrete transitions, use `useReducer` with a discriminated-union action type rather than many `useState` calls.
|
|
77
37
|
|
|
78
38
|
## Error Handling
|
|
39
|
+
- Surface every error: log and handle, or propagate — never swallow.
|
|
40
|
+
- **Fail fast:** on an invalid state, throw rather than returning a silent fallback.
|
|
41
|
+
- Represent expected failures as values with a `Result` type; reserve `throw` for unexpected/unrecoverable cases.
|
|
42
|
+
- Use purpose-specific error classes extending a base `AppError` carrying a `code` (e.g. ValidationError, ApiError, NotFoundError).
|
|
43
|
+
- **Layer responsibilities:** the API layer converts transport errors into domain errors; hooks propagate `AppError` upward; an Error Boundary catches render-time errors and shows fallback UI.
|
|
44
|
+
- Never log secrets (password, token, apiKey, creditCard).
|
|
79
45
|
|
|
80
|
-
**Absolute Rule**: Error suppression prohibited. All errors must have log output and appropriate handling.
|
|
81
|
-
|
|
82
|
-
**Fail-Fast Principle**: Fail quickly on errors to prevent continued processing in invalid states
|
|
83
|
-
```typescript
|
|
84
|
-
// Prohibited: Unconditional fallback
|
|
85
|
-
catch (error) {
|
|
86
|
-
return defaultValue // Hides error
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Required: Explicit failure
|
|
90
|
-
catch (error) {
|
|
91
|
-
logger.error('Processing failed', error)
|
|
92
|
-
throw error // Handle with Error Boundary or higher layer
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
**Result Type Pattern**: Express errors with types for explicit handling
|
|
97
46
|
```typescript
|
|
98
47
|
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }
|
|
99
48
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
49
|
+
class AppError extends Error {
|
|
50
|
+
constructor(message: string, readonly code: string, readonly statusCode = 500) {
|
|
51
|
+
super(message); this.name = this.constructor.name
|
|
52
|
+
}
|
|
104
53
|
}
|
|
105
54
|
```
|
|
106
55
|
|
|
107
|
-
|
|
56
|
+
Error Boundary — the one place a class component is required:
|
|
108
57
|
```typescript
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
58
|
+
class ErrorBoundary extends React.Component<{ children: React.ReactNode; fallback: React.ReactNode }, { hasError: boolean }> {
|
|
59
|
+
state = { hasError: false }
|
|
60
|
+
static getDerivedStateFromError() { return { hasError: true } }
|
|
61
|
+
render() { return this.state.hasError ? this.props.fallback : this.props.children }
|
|
114
62
|
}
|
|
115
|
-
// Purpose-specific: ValidationError(400), ApiError(502), NotFoundError(404)
|
|
116
63
|
```
|
|
117
64
|
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
**
|
|
124
|
-
Never include sensitive information (password, token, apiKey, secret, creditCard) in logs
|
|
125
|
-
|
|
126
|
-
**Asynchronous Error Handling in React**
|
|
127
|
-
- Error Boundary setup mandatory: Catch rendering errors
|
|
128
|
-
- Use try-catch with all async/await in event handlers
|
|
129
|
-
- Always log and re-throw errors or display error state
|
|
130
|
-
|
|
131
|
-
## Performance Optimization
|
|
132
|
-
|
|
133
|
-
- Component Memoization: Use React.memo for expensive components
|
|
134
|
-
- State Optimization: Minimize re-renders with proper state structure
|
|
135
|
-
- Lazy Loading: Use React.lazy and Suspense for code splitting
|
|
136
|
-
- Bundle Size: Monitor with the `build` script and keep under 500KB
|
|
65
|
+
## Project Conventions
|
|
66
|
+
- **Environment variables:** read through the build tool's env system (`process.env` is absent in the browser). Keep all secrets server-side — frontend code ships to the client.
|
|
67
|
+
- **Bundle & performance:** monitor with the `build` script, keep under 500KB; memoize expensive components (`React.memo`); code-split with `React.lazy` + `Suspense`; structure state to minimize re-renders.
|
|
68
|
+
- **Naming:** components/types `PascalCase`; variables/functions `camelCase`; hooks `use`-prefixed; constants `SCREAMING_SNAKE_CASE`.
|
|
69
|
+
- **Imports:** absolute paths from `src/`; order: React → external libs → internal (absolute) → internal (relative) → type-only → styles/assets.
|
|
70
|
+
- **Formatting:** follow Biome (semicolons and style come from project config).
|
|
@@ -140,8 +140,10 @@ describe('Button', () => {
|
|
|
140
140
|
|
|
141
141
|
## Test Design Patterns
|
|
142
142
|
|
|
143
|
+
Test user-visible results, not implementation details. Query by accessibility (`getByRole`/`getByLabelText`/`getByText`), not `getByTestId` or `container.querySelector`. Cover empty, error, and loading/async states, not only the happy path; await async UI with `findBy*`.
|
|
144
|
+
|
|
143
145
|
```typescript
|
|
144
|
-
//
|
|
146
|
+
// Test the user-visible result
|
|
145
147
|
it('increments count when clicked', async () => {
|
|
146
148
|
const user = userEvent.setup()
|
|
147
149
|
render(<Counter />)
|
|
@@ -149,10 +151,10 @@ it('increments count when clicked', async () => {
|
|
|
149
151
|
expect(screen.getByText('Count: 1')).toBeInTheDocument()
|
|
150
152
|
})
|
|
151
153
|
|
|
152
|
-
//
|
|
153
|
-
it('
|
|
154
|
-
|
|
155
|
-
render(<
|
|
156
|
-
|
|
154
|
+
// Error state: override the handler for one test
|
|
155
|
+
it('shows an error message on API failure', async () => {
|
|
156
|
+
server.use(http.get('/api/users', () => new HttpResponse(null, { status: 500 })))
|
|
157
|
+
render(<UserList />)
|
|
158
|
+
expect(await screen.findByText('Something went wrong')).toBeInTheDocument()
|
|
157
159
|
})
|
|
158
160
|
```
|
|
@@ -45,6 +45,8 @@ The two E2E lanes are budgeted independently — having a fixture-e2e for a jour
|
|
|
45
45
|
|
|
46
46
|
### Required Comment Format
|
|
47
47
|
|
|
48
|
+
The committed skeleton imports only the test framework (for `describe`/`it`/`it.todo`); the module under test is imported by the implementing task, never by the skeleton — a skeleton that references a not-yet-created module can fail gates that type-check, compile, or load test files before implementation begins.
|
|
49
|
+
|
|
48
50
|
Each test MUST include the following annotations.
|
|
49
51
|
|
|
50
52
|
```typescript
|
|
@@ -216,14 +216,9 @@ According to scale determination:
|
|
|
216
216
|
|
|
217
217
|
Note: At Small scale the implementation step still runs through task-executor with the standard 4-step cycle (`task-executor → escalation judgment → quality-fixer → commit`). Direct orchestrator edits are not used.
|
|
218
218
|
|
|
219
|
-
###
|
|
219
|
+
### Optional Preflight
|
|
220
220
|
|
|
221
|
-
For Medium / Large scale, after Batch approval
|
|
222
|
-
- `pending` — initial state set by work-planner
|
|
223
|
-
- `ready` — readiness verification has completed with no remaining gaps; safe to start the task execution cycle
|
|
224
|
-
- `escalated` — readiness verification has completed but residual gaps require user judgment before execution
|
|
225
|
-
|
|
226
|
-
External orchestration owns both the producer that promotes the marker beyond `pending` and the consumer that reads it before invoking task-executor. This guide does not invoke any orchestrator above the agent layer; agents read/write the marker only when explicitly asked.
|
|
221
|
+
For Medium / Large scale, after Batch approval implementation proceeds directly. Verifying the plan is implementable end-to-end (verification-strategy references, fixtures, UI rendering surface, E2E/local lane environment) is an optional preflight the user runs at their discretion via the prepare-implementation recipe, which exits no-op when readiness criteria already pass. This guide does not invoke any orchestrator above the agent layer.
|
|
227
222
|
|
|
228
223
|
## Cross-Layer Orchestration
|
|
229
224
|
|
|
@@ -192,21 +192,19 @@ skills:
|
|
|
192
192
|
# Frontend-specific Skills
|
|
193
193
|
frontend-typescript-rules:
|
|
194
194
|
skill: "frontend-typescript-rules"
|
|
195
|
-
tags: [frontend, react, implementation, type-safety,
|
|
196
|
-
typical-use: "React component creation, Props
|
|
195
|
+
tags: [frontend, react, implementation, type-safety, component-design, props-driven, hooks, error-handling, conventions]
|
|
196
|
+
typical-use: "React component creation, Props/state design, error handling, frontend TypeScript implementation rules"
|
|
197
197
|
size: small
|
|
198
198
|
key-references:
|
|
199
|
-
- "React Function Components - React
|
|
200
|
-
- "
|
|
201
|
-
- "
|
|
202
|
-
- "Clean Code - Robert C. Martin"
|
|
203
|
-
- "TypeScript satisfies Operator - Microsoft"
|
|
199
|
+
- "React Function Components - React docs"
|
|
200
|
+
- "Type guards at external boundaries (unknown narrowing)"
|
|
201
|
+
- "Result type and Error Boundary error handling"
|
|
204
202
|
sections:
|
|
205
|
-
- "
|
|
206
|
-
- "Type Safety
|
|
207
|
-
- "
|
|
203
|
+
- "Anti-patterns and Thresholds"
|
|
204
|
+
- "Type Safety at Boundaries"
|
|
205
|
+
- "Component and State Design"
|
|
208
206
|
- "Error Handling"
|
|
209
|
-
- "
|
|
207
|
+
- "Project Conventions"
|
|
210
208
|
|
|
211
209
|
frontend-typescript-testing:
|
|
212
210
|
skill: "frontend-typescript-testing"
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
想定影響範囲: Xファイル
|
|
6
6
|
関連Issue/PR: #XXX(該当する場合)
|
|
7
7
|
レビュースコープ: [Design Docとタスク対象から導出した変更予定ファイルの範囲。既存作業に対する改訂計画の場合はベースブランチ + diff範囲]
|
|
8
|
-
<!-- 下記の行はmedium / large規模のみ — small規模はこのplan-templateではなくtask-templateを使用する。値の行は末尾コメントを付けず、下流のパーサがbare statusの抽出(pending | ready | escalated)を行えるように保つこと。 -->
|
|
9
|
-
Implementation Readiness: pending
|
|
10
8
|
|
|
11
9
|
## 関連ドキュメント
|
|
12
10
|
- 設計書:
|
|
@@ -17,10 +17,10 @@ TypeScriptベースのReactアプリケーション実装。アーキテクチ
|
|
|
17
17
|
- デフォルト値設定と必須チェックの適切な実装
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
|
-
//
|
|
20
|
+
// ビルドツールの環境変数(公開値のみ。クライアント公開変数は VITE_ 接頭辞が必要)
|
|
21
21
|
const config = {
|
|
22
|
-
apiUrl: import.meta.env.
|
|
23
|
-
appName: import.meta.env.
|
|
22
|
+
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
|
23
|
+
appName: import.meta.env.VITE_APP_NAME || 'My App'
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// フロントエンドでは動作しない
|
|
@@ -5,311 +5,66 @@ description: React/TypeScriptの型安全性、コンポーネント設計、状
|
|
|
5
5
|
|
|
6
6
|
# TypeScript 開発ルール(フロントエンド)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
実装向けの frontend 固有 React/TypeScript ルール: しきい値、境界での型安全性、コンポーネント/状態の設計、エラーハンドリング、プロジェクト規約。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
10
|
+
## アンチパターンとしきい値
|
|
11
|
+
設計変更を促すシグナル:
|
|
12
|
+
- prop drilling が 3 階層以上 → Context または状態管理へ持ち上げる
|
|
13
|
+
- コンポーネントが 300 行超 → 分割する
|
|
14
|
+
- Props が 10 個超 → コンポーネントを分割(3〜7 個が適正範囲)
|
|
15
|
+
- optional Props が 50% 超 → デフォルト値または Context を導入する
|
|
16
|
+
- Props のネストが 2 階層超 → フラット化する
|
|
17
|
+
- 同一の `as` アサーションが 3 回以上出現 → 型設計を見直す
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
```
|
|
19
|
+
## 境界での型安全性
|
|
20
|
+
`any` を禁止する。型が得られない場合は `unknown` で受けて型ガードで絞り込む。`as` は最小化する(やむを得ない場合は理由をコメント)。
|
|
80
21
|
|
|
81
|
-
|
|
22
|
+
アプリ内部では React の Props/State は型保証されており `unknown` は不要。外部境界では必ず `unknown` で受け、使用前に型ガードで絞り込む: API レスポンス、`localStorage`/`sessionStorage`、URL パラメータ、パースした JSON。制御コンポーネントのフォーム入力は React 合成イベントを通じて型安全に保たれる。
|
|
82
23
|
|
|
83
24
|
```typescript
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
}
|
|
25
|
+
const raw: unknown = await (await fetch(url)).json()
|
|
26
|
+
if (!isUser(raw)) throw new ValidationError('invalid user')
|
|
27
|
+
const user = raw // User に絞り込み済み
|
|
153
28
|
```
|
|
154
29
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
```
|
|
30
|
+
## コンポーネントと状態の設計
|
|
31
|
+
- **Function component のみ。** class component は Error Boundary に限り許可(hook の代替が存在しないため)。
|
|
32
|
+
- **Props は名前付き型で明示**し分割代入する: `function UserCard({ user, onSelect }: UserCardProps)`。`React.FC` は使わず、props を関数に直接型付けして Props 契約を明示する。
|
|
33
|
+
- **Props 駆動:** 依存は Props で渡す。グローバル状態や Context へは必要なときだけアクセスする。
|
|
34
|
+
- **Custom hook** をロジック再利用と依存注入の単位とする(テスト容易性のため、協調オブジェクトは hook 経由で注入する)。
|
|
35
|
+
- **関数引数:** 位置引数は 0〜2 個。3 個以上は単一の options オブジェクトで受ける。
|
|
36
|
+
- **状態の形:** 状態は明示的に型付けする。複数フィールドかつ離散的な遷移を持つ状態は、複数の `useState` ではなく discriminated union の action 型を用いた `useReducer` にする。
|
|
187
37
|
|
|
188
38
|
## エラーハンドリング
|
|
189
|
-
|
|
190
|
-
|
|
39
|
+
- すべてのエラーを表に出す: ログして処理するか伝播する — 握り潰さない。
|
|
40
|
+
- **Fail fast:** 不正な状態では、無言のフォールバックを返さず throw する。
|
|
41
|
+
- 想定内の失敗は `Result` 型で値として表現する。`throw` は想定外/回復不能なケースに限る。
|
|
42
|
+
- 目的別のエラークラスは `code` を持つ基底 `AppError` を継承する(例: ValidationError, ApiError, NotFoundError)。
|
|
43
|
+
- **層の責務:** API 層は transport エラーをドメインエラーへ変換する。hook は `AppError` を上位へ伝播する。Error Boundary はレンダリング時のエラーを捕捉しフォールバック UI を表示する。
|
|
44
|
+
- 機微情報(password, token, apiKey, creditCard)をログに出さない。
|
|
191
45
|
|
|
192
46
|
```typescript
|
|
193
|
-
|
|
194
|
-
children: React.ReactNode
|
|
195
|
-
fallback: React.ReactNode
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
interface ErrorBoundaryState {
|
|
199
|
-
hasError: boolean
|
|
200
|
-
}
|
|
47
|
+
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }
|
|
201
48
|
|
|
202
|
-
class
|
|
203
|
-
|
|
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
|
|
49
|
+
class AppError extends Error {
|
|
50
|
+
constructor(message: string, readonly code: string, readonly statusCode = 500) {
|
|
51
|
+
super(message); this.name = this.constructor.name
|
|
212
52
|
}
|
|
213
53
|
}
|
|
214
54
|
```
|
|
215
55
|
|
|
216
|
-
|
|
217
|
-
|
|
56
|
+
Error Boundary — class component が必要となる唯一の箇所:
|
|
218
57
|
```typescript
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
58
|
+
class ErrorBoundary extends React.Component<{ children: React.ReactNode; fallback: React.ReactNode }, { hasError: boolean }> {
|
|
59
|
+
state = { hasError: false }
|
|
60
|
+
static getDerivedStateFromError() { return { hasError: true } }
|
|
61
|
+
render() { return this.state.hasError ? this.props.fallback : this.props.children }
|
|
267
62
|
}
|
|
268
63
|
```
|
|
269
64
|
|
|
270
|
-
##
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
-
|
|
274
|
-
-
|
|
275
|
-
-
|
|
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
|
-
```
|
|
65
|
+
## プロジェクト規約
|
|
66
|
+
- **環境変数:** ビルドツールの環境変数システム経由で読む(ブラウザに `process.env` は存在しない)。秘密情報はすべてサーバーサイドに置く — フロントエンドのコードはクライアントに配信される。
|
|
67
|
+
- **バンドルとパフォーマンス:** `build` スクリプトで監視し 500KB 未満に保つ。高コストなコンポーネントは `React.memo` でメモ化する。`React.lazy` + `Suspense` でコード分割する。再レンダリングを最小化する状態構造にする。
|
|
68
|
+
- **命名:** コンポーネント/型は `PascalCase`、変数/関数は `camelCase`、hook は `use` 接頭辞、定数は `SCREAMING_SNAKE_CASE`。
|
|
69
|
+
- **インポート:** `src/` からの絶対パス。順序: React → 外部ライブラリ → 内部(絶対)→ 内部(相対)→ 型のみ → スタイル/アセット。
|
|
70
|
+
- **フォーマット:** Biome に従う(セミコロンやスタイルはプロジェクト設定に従う)。
|