diff --git a/.claude/agents/analyzer.md b/.claude/agents/analyzer.md new file mode 100644 index 000000000..54c411006 --- /dev/null +++ b/.claude/agents/analyzer.md @@ -0,0 +1,56 @@ +--- +name: analyzer +description: 기존 코드를 분석하고 설명합니다. 코드 구조 파악, 함수 동작 이해, 파일 관계 파악이 필요할 때 호출됩니다. +tools: Read, Grep, Glob +--- + +당신은 코드 분석가입니다. + +## 역할 + +- 소스코드 구조 분석 +- 함수/컴포넌트 동작 설명 +- 파일 간 관계 설명 +- 데이터 흐름 파악 + +## 분석 시 포함할 내용 + +### 파일 분석 + +- 파일의 목적 +- 주요 export +- 의존성 (import) +- 다른 파일과의 관계 + +### 함수/컴포넌트 분석 + +- 역할/목적 +- 파라미터 설명 +- 반환값 +- 사용 예시 +- 관련 함수 + +## 응답 형식 +``` +📂 [파일명] 분석 + +## 목적 + +[이 파일이 하는 일] + +## 구조 + +[주요 구성 요소] + +## 핵심 코드 + +[중요한 부분 설명] + +## 관련 파일 + +- [연관 파일들] +``` + +## 원칙 +- 설명만 하고 수정 제안은 하지 않음 +- 학습자가 이해할 수 있는 수준으로 설명 diff --git a/.claude/agents/checker.md b/.claude/agents/checker.md new file mode 100644 index 000000000..c51f903df --- /dev/null +++ b/.claude/agents/checker.md @@ -0,0 +1,55 @@ +--- +name: checker +description: 과제 구현을 검증하고 피드백을 제공합니다. 구현 확인, 테스트, 요구사항 충족 여부 확인이 필요할 때 호출됩니다. +tools: Read, Grep, Glob, Bash +--- + +당신은 과제 검증자입니다. + +## 역할 + +- 구현 결과 검증 +- 요구사항 충족 확인 +- 코드 품질 피드백 +- 개선점 제안 + +## 검증 체크리스트 + +1. [ ] 요구사항 충족 여부 +2. [ ] 코드 동작 여부 +3. [ ] 에러 없음 +4. [ ] 유의점 준수 여부 + +## 응답 형식 + +### 통과 시 +``` +✅ 검증 통과! + +## 잘한 점 + +- [잘한 것 1] +- [잘한 것 2] + +## 개선 제안 (선택) + +- [더 좋게 할 수 있는 점] + +➡️ /project:done 으로 태스크를 완료하세요. +``` + +### 미통과 시 +``` +❌ 수정 필요 + +## 문제점 + +- [문제 1] +- [문제 2] + +## 수정 방향 + +- [어떻게 고쳐야 하는지] + +힌트가 필요하면 /project:hint 를 사용하세요. +``` diff --git a/.claude/agents/guide.md b/.claude/agents/guide.md new file mode 100644 index 000000000..e0a1fbeef --- /dev/null +++ b/.claude/agents/guide.md @@ -0,0 +1,54 @@ +--- +name: guide +description: 학습 가이드와 힌트를 제공합니다. 막혔을 때, 개념 설명이 필요할 때, 접근 방법을 모를 때 호출됩니다. +tools: Read, Grep, Glob, WebSearch, WebFetch +--- + +당신은 학습 가이드입니다. + +## 역할 + +- 개념 설명 +- 단계적 힌트 제공 +- 접근 방법 제안 +- 참고 자료 안내 + +## 힌트 제공 원칙 + +### 절대 금지 + +- 정답 코드 전체를 바로 제공 +- 구현을 대신 해주기 +- 복사-붙여넣기만 하면 되는 코드 제공 + +### 해야 할 것 + +- 단계적 힌트 (Level 1 → 2 → 3 → 4) +- 스스로 생각하게 유도하는 질문 +- 관련 개념 설명 +- 공식 문서 참조 안내 + +## 힌트 레벨 + +**Level 1** (방향성): +"이 문제는 [개념]을 활용하면 됩니다" + +**Level 2** (구체적 방향): +"[개념]을 사용해서 [구체적 접근]을 해보세요" + +**Level 3** (코드 스니펫): +"[핵심 코드 패턴] 형태로 시작해보세요" + +**Level 4** (거의 정답, 최후의 수단): +"[구체적 코드]를 추가하면 됩니다" + +## 힌트 요청 시 응답 형식 +``` +💡 힌트 (Level [N]) + +[힌트 내용] + +--- + +더 구체적인 힌트가 필요하면 말씀해주세요. +``` diff --git a/.claude/agents/task-manager.md b/.claude/agents/task-manager.md new file mode 100644 index 000000000..1713ebdfa --- /dev/null +++ b/.claude/agents/task-manager.md @@ -0,0 +1,82 @@ +--- +name: task-manager +description: 학습 태스크 관리를 담당합니다. 태스크 시작, 완료, 진행 상황 확인, 커밋 안내가 필요할 때 호출됩니다. +tools: Read, Write, Edit, Bash, Glob, Grep +--- + +당신은 학습 태스크 매니저입니다. + +## 역할 + +- 태스크 목록 관리 (`.claude/state/tasks.md`) +- 진행 상황 추적 (`.claude/state/progress.json`) +- 로그 작성 (`.claude/state/logs/`) +- 세션 관리 +- 커밋 안내 + +## 세션 시작 시 (/start) + +1. 오늘 날짜의 세션 로그 확인/생성: `.claude/state/logs/session-YYYY-MM-DD.md` +2. `.claude/state/progress.json`에서 현재 상태 확인 +3. `.claude/state/tasks.md`에서 현재 태스크 확인 +4. 이전 세션의 "다음 세션에서 할 일" 확인 +5. 세션 로그에 시작 시간 기록 +6. 사용자에게 현재 상황 안내 + +## 세션 종료 시 (/end) + +1. 오늘 진행한 내용 요약 +2. 세션 로그 업데이트: + - 진행한 내용 정리 + - 커밋 내역 추가 (`git log --oneline`로 확인) + - 미완료 작업을 "다음 세션에서 할 일"에 기록 + - 세션 종료 시간 기록 +3. `.claude/state/progress.json` lastUpdated 갱신 +4. 커밋되지 않은 변경사항 확인 및 제안 +5. 사용자에게 요약 안내 + +## 태스크 완료 시 (/done) + +1. `.claude/state/logs/task-[n].md` 작성 +2. `.claude/state/tasks.md` 업데이트 (체크 표시) +3. `.claude/state/progress.json` 업데이트 +4. 세션 로그에도 완료 내용 기록 +5. 커밋 메시지 제안: +``` +Type: 내용 + +- 세부 내용 +- 세부 내용 +``` +6. 다음 태스크 안내 + +## 힌트 사용 시 (/hint) + +1. `.claude/state/progress.json`의 hintsUsed 업데이트 +2. 세션 로그에 힌트 사용 기록 + +## 커밋 시 (/commit) + +1. 세션 로그의 "커밋 내역"에 추가 + +## 커밋 메시지 규칙 + +- Type은 영어 대문자로 시작: Feat, Fix, Refactor, Style, Docs, Test, Chore +- 내용은 한글로 작성 +- 세부 내용은 * 로 나열 + +## 로그 파일 구조 + +``` +.claude/state/logs/ +├── task-1.md # 완료된 태스크 로그 +├── task-2.md # 완료된 태스크 로그 +├── session-2025-12-08.md # 일일 세션 로그 +└── session-2025-12-09.md # 일일 세션 로그 +``` + +## 금지사항 + +- 코드 직접 작성 금지 (guide에게 위임) +- 로그 없이 태스크 완료 처리 금지 +- 세션 로그 없이 세션 종료 금지 diff --git a/.claude/commands/check.md b/.claude/commands/check.md new file mode 100644 index 000000000..9b2eb3bbe --- /dev/null +++ b/.claude/commands/check.md @@ -0,0 +1,13 @@ +--- +description: 과제 구현을 검증합니다 +--- + +현재 구현을 검증하고 피드백을 제공합니다. + +## 검증 항목 + +- 요구사항 충족 +- 코드 동작 여부 +- 유의점 준수 + +$ARGUMENTS diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md new file mode 100644 index 000000000..9cd4ae79d --- /dev/null +++ b/.claude/commands/commit.md @@ -0,0 +1,28 @@ +--- +description: 커밋 메시지를 생성합니다 +--- + +현재까지의 변경사항을 기반으로 커밋 메시지를 생성합니다. + +## 커밋 메시지 형식 +``` +Type: 내용 + +- 세부 내용 +- 세부 내용 +``` + +## Type 종류 +- Feat: 새로운 기능 +- Fix: 버그 수정 +- Refactor: 리팩토링 +- Style: 스타일 변경 +- Docs: 문서 수정 +- Test: 테스트 +- Chore: 기타 + +## 규칙 +- Type은 영어 대문자로 시작 +- 내용은 한글로 작성 + +$ARGUMENTS diff --git a/.claude/commands/done.md b/.claude/commands/done.md new file mode 100644 index 000000000..4a61adac9 --- /dev/null +++ b/.claude/commands/done.md @@ -0,0 +1,13 @@ +--- +description: 현재 태스크를 완료 처리합니다 +--- + +현재 태스크를 완료하고 다음 작업을 수행합니다: + +1. **로그 작성**: `.claude/state/logs/task-[n].md` +2. **태스크 목록 업데이트**: `.claude/state/tasks.md` +3. **진행 상황 업데이트**: `.claude/state/progress.json` +4. **커밋 메시지 제안** +5. **다음 태스크 안내** + +$ARGUMENTS diff --git a/.claude/commands/end.md b/.claude/commands/end.md new file mode 100644 index 000000000..607ab2912 --- /dev/null +++ b/.claude/commands/end.md @@ -0,0 +1,38 @@ +--- +description: 학습 세션을 종료합니다 +--- + +학습 세션을 종료합니다. + +## 수행할 작업 + +1. **오늘 진행 내용 요약**: + - 현재 태스크 진행 상황 파악 + - 완료한 작업 목록 정리 + - 미완료 작업 목록 정리 + +2. **세션 로그 업데이트**: `.claude/state/logs/session-YYYY-MM-DD.md` + - 세션 종료 시간 기록 + - 오늘 진행한 내용 정리 + - 커밋 내역 추가 (git log로 확인) + - "다음 세션에서 할 일" 작성 + +3. **progress.json 업데이트**: + - lastUpdated 시간 갱신 + +4. **커밋 제안** (선택): + - 커밋되지 않은 변경사항이 있으면 커밋 제안 + +5. **사용자에게 안내**: + - 오늘 진행 요약 + - 다음에 이어서 할 내용 + - 수고했다는 인사 + +## 세션 종료 시 체크리스트 + +- [ ] 세션 로그에 진행 내용 기록됨 +- [ ] 미완료 작업이 "다음 세션에서 할 일"에 기록됨 +- [ ] 커밋되지 않은 변경사항 확인 +- [ ] progress.json 업데이트됨 + +$ARGUMENTS diff --git a/.claude/commands/hint.md b/.claude/commands/hint.md new file mode 100644 index 000000000..b5af92c74 --- /dev/null +++ b/.claude/commands/hint.md @@ -0,0 +1,14 @@ +--- +description: 현재 태스크에 대한 힌트를 요청합니다 +--- + +현재 진행 중인 태스크에 대한 힌트를 제공합니다. + +## 힌트 레벨 + +- 기본: Level 1 (방향성 힌트) +- "더 자세히": Level 2 +- "더 구체적으로": Level 3 +- "거의 답": Level 4 + +$ARGUMENTS diff --git a/.claude/commands/setup.md b/.claude/commands/setup.md new file mode 100644 index 000000000..9485fa0cb --- /dev/null +++ b/.claude/commands/setup.md @@ -0,0 +1,7 @@ +--- +description: 학습 과제 초기 설정을 시작합니다 +--- + +SETTING.md를 읽고 학습 과제 환경을 설정합니다. + +$ARGUMENTS diff --git a/.claude/commands/start.md b/.claude/commands/start.md new file mode 100644 index 000000000..d53d02543 --- /dev/null +++ b/.claude/commands/start.md @@ -0,0 +1,65 @@ +--- +description: 학습 세션을 시작합니다 +--- + +학습 세션을 시작합니다. + +## 수행할 작업 + +1. **세션 로그 생성/확인**: `.claude/state/logs/session-YYYY-MM-DD.md` + - 오늘 날짜의 세션 로그가 없으면 새로 생성 + - 이미 있으면 이어서 작성 (재시작 기록) + +2. **현재 상태 확인**: + - `.claude/state/progress.json` 읽기 + - `.claude/state/tasks.md`에서 현재 태스크 확인 + - 이전 세션 로그가 있다면 마지막 진행 상황 확인 + +3. **세션 로그에 기록**: + - 세션 시작 시간 + - 현재 진행 중인 태스크 + - 이전 세션에서 남긴 "다음에 할 일" 확인 + +4. **사용자에게 안내**: + - 현재 태스크 요약 + - 이전 세션에서 미완료된 작업 + - 오늘 할 일 제안 + +## 세션 로그 템플릿 + +```markdown +# 세션 로그: YYYY-MM-DD + +## 세션 정보 + +- **시작**: YYYY-MM-DD HH:MM +- **현재 태스크**: Task N (태스크명) +- **상태**: 진행 중 + +## 이전 세션 이어서 + +- [이전 미완료 작업들] + +## 오늘 진행한 내용 + +### [작업 내용] + +## 힌트 사용 + +- Level N: [내용] + +## 커밋 내역 + +| 해시 | 메시지 | +|------|--------| + +## 다음 세션에서 할 일 + +- + +## 메모 + +- +``` + +$ARGUMENTS diff --git a/.claude/state/learning.md b/.claude/state/learning.md new file mode 100644 index 000000000..4349f795e --- /dev/null +++ b/.claude/state/learning.md @@ -0,0 +1,40 @@ +# 📚 학습 정보 + +## 🎯 학습 목표 + +단일책임원칙이 적용된 좋은 폴더구조를 안다 + +## ⚠️ 유의점 + +- 참고 자료 중 제공된 외부 링크는 처음 제공될 때 링크를 읽은 후 내용을 정리한 마크다운 문서를 생성하여 `docs/` 아래 배치하고 이후 생성된 문서를 참고하도록 설정할 것. + +## 📖 참고 자료 + +### 소스코드 + +- 현재 프로젝트 파일 전체 + +### 문서 + +- `docs/` 아래 위치한 모든 문서: + - `docs/FSD-Mental-model.md` - FSD 멘탈 모델 가이드 (레이어, 슬라이스, 세그먼트 개념) + - `docs/Evaluation-criteria.md` - 과제 평가 기준 + - `docs/3-8-FSD Architecture.md` - FSD 아키텍처의 배경과 의의 (Layer, Segment 상세 설명) + - `docs/3-9-TanStackQuery.md` - TanStack Query와 선언적 프로그래밍 (서버상태관리) + - `docs/3-10-2025 Frontend Folder Structure Model.md` - 2025 프론트엔드 폴더 구조 모델 (아키텍처 비교 분석) + - `docs/3-11-Tips for Assignments.md` - 과제를 위한 팁 (Jotai/Zustand, FSD 경계, Props 가이드라인) + - `docs/3-12-The significance of FSD learning.md` - FSD 학습의 의의 (멘탈모델로서의 FSD) +- README.md + +### 외부 링크 (원본) + +- https://www.notion.so/teamsparta/3-8-FSD-2722dc3ef514800a8040dbc72855877a +- https://www.notion.so/teamsparta/3-9-TanStackQuery-2722dc3ef514801ea229c4bfccbf105c +- https://www.notion.so/teamsparta/3-10-2025-2722dc3ef5148079b40edaad5579d4e6 +- https://www.notion.so/teamsparta/3-11-2722dc3ef514807883a6d0e9c58d6c80 +- https://www.notion.so/teamsparta/3-12-FSD-2722dc3ef51480bc8996d8bf523b7a1f + +--- + +**설정일**: 2025-12-08 +**문서 업데이트**: 2025-12-08 (외부 링크 문서화 완료) diff --git a/.claude/state/logs/session-2025-12-09.md b/.claude/state/logs/session-2025-12-09.md new file mode 100644 index 000000000..0a40bcd7a --- /dev/null +++ b/.claude/state/logs/session-2025-12-09.md @@ -0,0 +1,70 @@ +# 세션 로그: 2025-12-09 + +## 세션 정보 + +- **시작**: 2025-12-09 +- **1차 종료**: 2025-12-09 02:29 (KST) +- **재시작**: 2025-12-09 02:32 (KST) +- **2차 종료**: 2025-12-10 01:09 (KST) +- **현재 태스크**: Task 3 (entities 레이어 구성) +- **상태**: 종료 (Task 2 완료, Task 3 대기) + +## 오늘 진행한 내용 + +### 1. Task 1 완료 +- 프로젝트 구조 분석 및 문제점 파악 +- `/done` 명령어로 완료 처리 + +### 2. Task 2 진행 (shared 레이어 구성) + +#### 완료한 작업 +- [x] `src/shared/ui/` 폴더 생성 +- [x] UI 컴포넌트 분리: Button, Card, Dialog, Input, Select, Table, Textarea +- [x] `src/shared/lib/` 폴더 생성 +- [x] `src/features/search/ui/HighlightText.tsx` 생성 +- [x] 기존 `src/components/index.tsx` 삭제 +- [x] `PostsManagerPage.tsx` import 경로 업데이트 +- [x] tsconfig, vite alias 설정 추가 + +#### 추가 완료 작업 (재시작 후) +- [x] UI 컴포넌트 타입 수정 (HTMLAttributes → InputHTMLAttributes 등) +- [x] entities 도메인 타입 정의 (post, user) +- [x] shared/api 구성 (client.ts, types.ts) +- [x] TypeScript 빌드 성공 + +#### 고민한 점 +1. **DialogHeader 타입**: `React.HTMLAttributes` 적용 +2. **highlightText 위치**: 검색 기능 종속적이므로 `features/search`로 결정 +3. **API 함수 위치**: 도메인별 API는 `entities/*/api`로, 공통 설정은 `shared/api`로 결정 + +#### 힌트 사용 +- Level 1: Task 1 분석 방향 힌트 +- Level 1: UI 컴포넌트 타입 힌트 (HTMLAttributes vs InputHTMLAttributes) +- Level 1: entities 폴더 구조 설명 + +## 커밋 내역 + +| 해시 | 메시지 | +|------|--------| +| `3339e80` | Refactor: Task 2 완료 - shared 레이어 및 도메인 타입 구성 | +| `77e7c2e` | Chore: 세션 종료 및 빌드 캐시 gitignore 추가 | +| `57dbea2` | Docs: SETTING.md v6 업데이트 - 세션 로그 시스템 반영 | +| `57f2c0a` | Feat: 세션 로그 시스템 추가 | +| `380bca2` | Chore: jotai 및 @types/node 패키지 추가 | + +### 3. Task 2 완료 +- UI 컴포넌트 타입 수정 완료 +- entities 도메인 타입 정의 완료 +- `pnpm build` 성공 + +## 다음 세션에서 할 일 + +1. Task 3 (entities 레이어 구성) 본격 진행 + - api 분리 + - ui 분리 + - model 완성 + +## 메모 + +- jotai 패키지 추가함 (Task 5 전역상태관리용) +- FSD 의존성 방향: shared → entities → features → widgets → pages diff --git a/.claude/state/logs/session-2025-12-10.md b/.claude/state/logs/session-2025-12-10.md new file mode 100644 index 000000000..120b8da6c --- /dev/null +++ b/.claude/state/logs/session-2025-12-10.md @@ -0,0 +1,66 @@ +# 세션 로그: 2025-12-10 + +## 세션 정보 + +- **시작**: 2025-12-10 +- **종료**: 2025-12-10 +- **현재 태스크**: Task 3 → Task 4 +- **상태**: 종료 (Task 3 완료) + +## 이전 세션 이어서 + +- Task 2 완료됨 (shared 레이어 및 도메인 타입 구성) +- entities 도메인 타입은 정의되어 있음 (post, user) +- Task 3에서 api, ui, model 분리 작업 필요 + +## 오늘 진행한 내용 + +### Task 3: entities 레이어 구성 완료 + +#### 1. 도메인별 폴더 구조 구성 +- Post, User, Comment, Tag 도메인 분리 +- 각 도메인에 model/types.ts, api/index.ts, index.ts 생성 + +#### 2. model/types.ts - 타입 정의 +- Post: Post, PostsData, NewPost +- User: User, UsersData +- Comment: Comment, CommentsData, NewComment +- Tag: Tag, TagsData + +#### 3. api/index.ts - API 함수 분리 +- Post API: fetchPosts, addPost, deletePost, searchPosts +- User API: fetchUsers +- Comment API: fetchComments, addComment, updateComment, deleteComment, fetchCommentsByPostId +- Tag API: fetchTags + +#### 4. FSD 의존성 방향 준수 +- entities는 shared만 의존 +- Data 타입은 shared/api/types.ts에 유지 +- entities 간 상호 의존 제거 + +#### 5. PostsManagerPage.tsx 수정 +- import 경로를 entities로 변경 +- 기존 shared/types에서 entities 각 도메인으로 분리 import + +## 힌트 사용 + +- 없음 + +## 커밋 내역 + +| 해시 | 메시지 | +|------|--------| +| `291ed3a` | Refactor: Task 3 완료 - entities 레이어 구성 | + +## 다음 세션에서 할 일 + +- Task 4: features 레이어 구성 + - 사용자 행동(이벤트 처리) 분리 + - feature별 ui 분리 + - feature별 api 분리 (entities/api 활용) + +## 메모 + +- ui segment는 Task 4 이후 정리 예정 +- PostWithAuthor 타입은 features 레이어에서 필요 시 정의 +- updatePost는 주석 처리 (features에서 재구현 예정) diff --git a/.claude/state/logs/session-2025-12-11.md b/.claude/state/logs/session-2025-12-11.md new file mode 100644 index 000000000..e8b406cfe --- /dev/null +++ b/.claude/state/logs/session-2025-12-11.md @@ -0,0 +1,80 @@ +# 세션 로그: 2025-12-11 + +## 세션 정보 + +- **시작**: 2025-12-11 +- **재시작**: 2025-12-11 (2차 세션) +- **종료**: 2025-12-12 +- **현재 태스크**: Task 4 (features 레이어 구성) +- **상태**: 구조 완료 (세부 수정 보류) + +## 이전 세션 이어서 + +- Task 3 완료됨 (entities 레이어 구성) +- Post, User, Comment, Tag 도메인별 폴더 구조 구성 완료 +- 각 도메인에 model/types.ts, api/index.ts, index.ts 생성 완료 +- FSD 의존성 방향 준수 확인 + +## 오늘 진행한 내용 + +### Task 4: features 레이어 구성 + +#### 1. 피처 목록 설계 +- add-post, edit-post, delete-post, post-filter, comment-section, user-info 6개 피처 정의 +- 각 피처에 ui/, model/, api/, index.ts 폴더 구조 생성 + +#### 2. 완료된 피처 +- **user-info**: UserInfoModal.tsx, UserProfile.tsx 구현 +- **add-post**: AddPostModal.tsx 구현 +- **edit-post**: EditPostModal.tsx 구현 +- **post-filter**: 합성 컴포넌트 패턴 적용 (PostFilter.tsx, Search.tsx, ByTag.tsx, BySort.tsx, ByOrder.tsx) +- **comment-section**: Comments.tsx, AddCommentModal.tsx, EditCommentModal.tsx 분리 +- ~~**delete-post**~~: 삭제 (별도 피처가 아님으로 결정, entities/post/api에 deletePost만 유지) + +#### 3. FSD 타입 설계 논의 +- PostWithAuthor (cross-cutting 타입) 위치 논의 +- 순수주의 vs 실용주의 접근 비교 +- @x 패턴 학습 및 적용 결정 + +#### 4. @x 패턴 적용 +- `entities/post/@x/with-user.ts` 생성 +- PostWithAuthor 타입을 @x 패턴으로 정의 +- entities/post/api에서 pages 의존성 제거 +- `import type` 사용하여 타입만 import + +#### 5. widgets 레이어 작업 (일부) +- PostTable.tsx를 widgets로 이동 +- UserProfile을 features/user-info에 배치 + +#### 6. /check 검증 수행 +- 빌드 실패 (32개 오류) +- FSD 구조는 완벽 +- 세부 수정 사항은 전체 태스크 완료 후 한번에 정리하기로 결정 + +## 힌트 사용 + +- Level 1: 4회 (user-info, add-post, edit-post, post-filter) +- Level 2: 1회 (user-info 구조) + +## 커밋 내역 + +| 해시 | 메시지 | +|------|--------| +| 67a690d | Refactor: Task 4 진행 - features 레이어 구성 | + +## 다음 세션에서 할 일 + +- [ ] PostsManagerPage import 경로 수정 (public API 사용) +- [ ] import type 일관성 확보 +- [ ] PostsManagerPage 로직 완성 (누락된 상태/함수) +- [ ] 빌드 테스트 및 오류 수정 +- [ ] Task 4 완료 커밋 +- [ ] Task 5 (전역상태관리) 시작 + +## 메모 + +- @x 패턴: entities 간 명시적 cross-reference를 표현하는 FSD 공식 패턴 +- PostWithAuthor는 entities/post/@x/with-user.ts에 위치 +- 순수주의 선택: entities는 순수하게 유지, 조합 타입은 @x로 명시적 표현 +- post-filter: 합성 컴포넌트 패턴(Compound Component Pattern) 적용 +- delete-post는 별도 피처가 아님 - 단순 API 호출은 entities에 위치 diff --git a/.claude/state/logs/session-2025-12-12.md b/.claude/state/logs/session-2025-12-12.md new file mode 100644 index 000000000..08c062dcd --- /dev/null +++ b/.claude/state/logs/session-2025-12-12.md @@ -0,0 +1,70 @@ +# 세션 로그: 2025-12-12 + +## 세션 정보 + +- **시작**: 2025-12-12 +- **재시작**: 2025-12-12 (2차 세션) +- **현재 태스크**: Task 5 (전역상태관리 적용) +- **상태**: 빌드 테스트 및 완료 진행 중 + +## 이전 세션 이어서 + +- Task 4 features 레이어 구조 완성 +- 6개 피처 정의: user-info, add-post, edit-post, post-filter, comment-section +- @x 패턴 학습 및 적용 완료 +- 빌드 오류 존재 (세부 수정 보류) + +## 오늘 진행한 내용 + +### Task 5: 전역상태관리 적용 (Jotai) + +#### 1. Jotai 선택 및 설치 +- Context API, Jotai, Zustand 중 **Jotai** 선택 +- Atomic 패턴으로 세분화된 상태 관리 +- `pnpm add jotai` 설치 완료 + +#### 2. Atom 설계 및 생성 (5개 도메인) + +| 도메인 | 파일 | Atoms | +|--------|------|-------| +| **post-filter** | `features/post-filter/model/atoms.ts` | searchQueryAtom, sortAtom, orderAtom, skipAtom, limitAtom | +| **tag** | `entities/tag/model/atoms.ts` | tagsAtom, selectedTagAtom | +| **post** | `entities/post/model/atoms.ts` | totalAtom, postsAtom, selectedPostAtom, newPostAtom, showAddDialogAtom, showEditDialogAtom, showPostDetailDialogAtom | +| **comment** | `entities/comment/model/atoms.ts` | commentsAtom, selectedCommentAtom, newCommentAtom, showAddCommentDialogAtom, showEditCommentDialogAtom | +| **user** | `entities/user/model/atoms.ts` | usersAtom, selectedUserAtom, showUserInfoAtom | + +#### 3. 모든 컴포넌트 useAtom 적용 +- PostFilter 하위 컴포넌트 (Search, ByTag, BySort, ByOrder) +- PostTable, PageNavigation +- AddPostModal, EditPostModal +- AddCommentModal, EditCommentModal +- PostDetailModal, UserInfoModal + +#### 4. Props Drilling 완전 제거 +- **Before**: 17개 useState + 46개 props +- **After**: 1개 useState (loading) + 0개 props +- PostsManagerPage가 깔끔하게 정리됨 + +## 힌트 사용 + +- Level 1: 3회 (Jotai 기본 사용법, Atom 생성 방법, FSD 배치 전략) + +## 커밋 내역 + +| 해시 | 메시지 | +|------|--------| +| 0f0fb91 | Refactor: Task 5 진행 - Jotai 전역상태관리 적용 | + +## 다음 세션에서 할 일 + +- [ ] 빌드 테스트 및 오류 수정 +- [ ] Task 5 완료 커밋 +- [ ] Task 6: widgets 레이어 구성 시작 +- [ ] 전체 FSD 구조 검토 + +## 메모 + +- Jotai의 useAtom은 useState와 동일한 API로 학습 비용이 낮음 +- useAtomValue (읽기만), useSetAtom (쓰기만)으로 리렌더링 최적화 가능 +- FSD에서 Atom 배치: entities/[domain]/model/atoms.ts 또는 features/[feature]/model/atoms.ts +- Props Drilling 제거로 컴포넌트 간 결합도가 크게 낮아짐 diff --git a/.claude/state/logs/task-1.md b/.claude/state/logs/task-1.md new file mode 100644 index 000000000..90a1aeb91 --- /dev/null +++ b/.claude/state/logs/task-1.md @@ -0,0 +1,52 @@ +# Task 1: 프로젝트 구조 분석 및 문제점 파악 + +## 정보 + +- **시작**: 2025-12-08 +- **완료**: 2025-12-08 +- **힌트 사용**: Level 1 x 1회 + +## 목표 + +- 현재 코드의 구조 파악 +- 컴포넌트 크기, Type 처리, 상태 관리 등 문제점 분석 + +## 완료 내용 + +### 발견한 문제점 + +1. **components/index.tsx** + - 모든 UI 컴포넌트가 구분 없이 한 파일에 뭉쳐있음 + - Button, Card, Dialog, Select, Table, Input, Textarea 등이 하나의 파일에 존재 + +2. **PostsManagerPage.tsx** + - 로컬 상태로 대부분 관리됨 + - 컴포넌트 분리가 안되어 있음 + - API 호출, 상태 관리, UI 렌더링 로직이 모두 뭉쳐있음 + - 매우 높은 결합도와 낮은 응집도 + +3. **재사용 모듈 부재** + - type 정의가 분리되어 있지 않음 + - util 등 재사용 가능한 모듈이 보이지 않음 + +### FSD 관점 분석 + +- 단일 책임 원칙(SRP) 위반 +- 관심사 분리 안됨 +- 의존성 방향 불명확 +- 도메인별 구분 없음 + +## 수정한 파일 + +- (분석 단계로 파일 수정 없음) + +## 배운 점 + +- FSD 리팩토링을 위해서는 먼저 현재 구조의 문제점을 명확히 파악해야 함 +- 결합도와 응집도 관점에서 코드를 분석하는 것이 중요 + +## 다음 단계 + +- Task 2: shared 레이어 구성 + - 공통 UI 컴포넌트 분리 + - 공통 유틸리티/로직 분리 diff --git a/.claude/state/logs/task-10.md b/.claude/state/logs/task-10.md new file mode 100644 index 000000000..fb614e39c --- /dev/null +++ b/.claude/state/logs/task-10.md @@ -0,0 +1,91 @@ +# Task 10: TanStack Query 마이그레이션 및 기능 복원 + +## 완료 일시 +2025-12-12 + +## 수행 내용 + +### 1. TanStack Query 설정 +- `@tanstack/react-query`, `@tanstack/react-query-devtools` 설치 +- `app/providers/QueryProvider.tsx` 생성 +- QueryClient 설정 (retry: 0, refetchOnWindowFocus: false) +- DevTools 설정 (개발 환경에서만 표시) + +### 2. Query/Mutation 훅 구현 + +#### entities/post/model/queries.ts +- `usePostsQuery`: 게시물 목록 조회 (users 데이터 병합) +- `useSearchPostsQuery`: 게시물 검색 +- `usePostsByTagQuery`: 태그별 게시물 조회 +- `useAddPostMutation`: 게시물 추가 (캐시 직접 업데이트) +- `useUpdatePostMutation`: 게시물 수정 (캐시 직접 업데이트) +- `useDeletePostMutation`: 게시물 삭제 (캐시 직접 업데이트) + +#### entities/comment/model/queries.ts +- `useCommentsQuery`: 댓글 목록 조회 +- `useAddCommentMutation`: 댓글 추가 (캐시 직접 업데이트) +- `useUpdateCommentMutation`: 댓글 수정 (캐시 직접 업데이트) +- `useDeleteCommentMutation`: 댓글 삭제 (캐시 직접 업데이트) +- `useLikeCommentMutation`: 댓글 좋아요 (캐시 직접 업데이트) + +#### entities/tag/model/queries.ts +- `useTagsQuery`: 태그 목록 조회 (staleTime 5분) + +#### entities/user/model/queries.ts +- `useUsersQuery`: 사용자 목록 조회 +- `useUserQuery`: 사용자 상세 조회 + +### 3. 누락 기능 복원 +- Posts에 Users 데이터 병합 (author 정보 표시) +- 태그별 게시물 필터링 구현 +- 검색 기능 TanStack Query 연동 +- 정렬 기능 구현 (sortBy, sortOrder) +- URL query params 동기화 복원 +- PostDetailModal 열기 기능 수정 +- UserProfile에 author prop 전달 + +### 4. Mock API 대응 +- DummyJSON은 실제 데이터를 저장하지 않는 mock API +- `invalidateQueries` 대신 `setQueryData`/`setQueriesData`로 캐시 직접 업데이트 +- CRUD 작업 시 클라이언트 캐시에서 데이터 관리 + +### 5. 프로덕션 배포 설정 +- `shared/api/config.ts` 생성 +- 개발 환경: `/api` (Vite proxy) +- 프로덕션 환경: `https://dummyjson.com` (직접 호출) +- `vite.config.ts` base path 수정 (`front_7th_chapter3-3`) + +## Query Keys 설계 +```typescript +// posts +postKeys.all: ["posts"] +postKeys.lists(): ["posts", "list"] +postKeys.list({ limit, skip }): ["posts", "list", { limit, skip }] +postKeys.search(query): ["posts", "search", query] +postKeys.byTag(tag): ["posts", "tag", tag] + +// comments +commentKeys.all: ["comments"] +commentKeys.list(postId): ["comments", "list", postId] + +// tags +tagKeys.all: ["tags"] + +// users +userKeys.all: ["users"] +userKeys.list(): ["users", "list"] +userKeys.detail(id): ["users", "detail", id] +``` + +## 학습 포인트 +- TanStack Query의 캐시 관리 전략 +- setQueryData vs invalidateQueries 사용 시점 +- Mock API 환경에서의 낙관적 업데이트 +- Query Keys 팩토리 패턴 +- 환경별 API base URL 설정 + +## 관련 커밋 +- Feat: Task 6~10 진행 - FSD 전체 레이어 구성 및 TanStack Query 적용 +- Fix: TanStack Query 캐시 직접 업데이트 및 누락 기능 복원 +- Fix: GitHub Pages base path 수정 +- Fix: 프로덕션 환경 API base URL 설정 diff --git a/.claude/state/logs/task-2.md b/.claude/state/logs/task-2.md new file mode 100644 index 000000000..ec8540d9c --- /dev/null +++ b/.claude/state/logs/task-2.md @@ -0,0 +1,63 @@ +# Task 2: shared 레이어 구성 + +## 완료일: 2025-12-09 + +## 목표 +- 공통 UI 컴포넌트 분리 +- 공통 유틸리티/로직 분리 + +## 완료한 작업 + +### 1. shared/ui 구성 +- `src/shared/ui/` 폴더 생성 +- UI 컴포넌트 분리: + - `Button.tsx` + - `Card.tsx` + - `Dialog.tsx` + - `Input.tsx` + - `Select.tsx` + - `Table.tsx` + - `Textarea.tsx` +- UI 컴포넌트 타입 수정: `HTMLAttributes` → `InputHTMLAttributes`, `TextareaHTMLAttributes` 등 적절한 타입으로 변경 + +### 2. shared/lib 구성 +- `src/shared/lib/types.ts` - 공통 타입 정의 + +### 3. shared/api 구성 +- `src/shared/api/client.ts` - API 클라이언트 설정 +- `src/shared/api/types.ts` - API 응답 타입 정의 + +### 4. entities 도메인 타입 정의 (Task 3 선행 작업) +- `src/entities/post/model/types.ts` - Post 관련 타입 +- `src/entities/user/model/types.ts` - User 관련 타입 +- `src/entities/post/index.ts` - public API + +### 5. features 구성 (선행 작업) +- `src/features/search/ui/HighlightText.tsx` - 검색 하이라이트 컴포넌트 + +### 6. 설정 파일 업데이트 +- `tsconfig.json`, `vite.config.ts` - alias 설정 추가 + +## 배운 점 + +1. **React HTML Attributes 타입 계층** + - `HTMLAttributes` → 모든 HTML 요소 공통 속성 + - `InputHTMLAttributes` → input 고유 속성 포함 (placeholder, value 등) + - `TextareaHTMLAttributes` → textarea 고유 속성 포함 (rows, cols 등) + +2. **FSD entities 폴더 구조** + - `model/` - 타입, 순수 함수, 상수 + - `api/` - 서버 통신 함수 + - `ui/` - 순수 표현 컴포넌트 + - `index.ts` - public API + +3. **타입 정의 시 interface vs type** + - 빈 interface는 ESLint 경고 발생 + - 커스텀 props가 없으면 type alias 사용이 깔끔 + +## 힌트 사용 +- Level 1: UI 컴포넌트 타입 힌트 (HTMLAttributes vs InputHTMLAttributes) +- Level 1: entities 폴더 구조 설명 + +## 빌드 결과 +- ✅ `pnpm build` 성공 diff --git a/.claude/state/logs/task-3.md b/.claude/state/logs/task-3.md new file mode 100644 index 000000000..5f93de555 --- /dev/null +++ b/.claude/state/logs/task-3.md @@ -0,0 +1,175 @@ +# Task 3 완료 로그: entities 레이어 구성 + +## 완료 일시 +- **시작**: 2025-12-10 +- **완료**: 2025-12-11 +- **소요 시간**: 약 1일 + +## 작업 내용 + +### 1. 도메인별 폴더 구조 구성 + +각 도메인(Post, User, Comment, Tag)을 독립적인 entities로 분리하고, FSD 아키텍처의 segment 구조(model, api, ui, index) 적용 + +``` +src/entities/ +├── post/ +│ ├── model/types.ts +│ ├── api/index.ts +│ └── index.ts +├── user/ +│ ├── model/types.ts +│ ├── api/index.ts +│ └── index.ts +├── comment/ +│ ├── model/types.ts +│ ├── api/index.ts +│ └── index.ts +└── tag/ + ├── model/types.ts + ├── api/index.ts + └── index.ts +``` + +### 2. model/types.ts - 도메인별 타입 정의 + +#### Post (src/entities/post/model/types.ts) +- `Post`: 기본 게시물 타입 +- `PostsData`: 게시물 목록 응답 타입 (Data 상속) +- `NewPost`: 게시물 생성용 타입 (Pick 유틸리티 활용) + +#### User (src/entities/user/model/types.ts) +- `User`: 기본 사용자 타입 +- `UsersData`: 사용자 목록 응답 타입 (Data 상속) + +#### Comment (src/entities/comment/model/types.ts) +- `Comment`: 기본 댓글 타입 (중첩된 user 정보 포함) +- `CommentsData`: 댓글 목록 응답 타입 (Data 상속) +- `NewComment`: 댓글 생성용 타입 + +#### Tag (src/entities/tag/model/types.ts) +- `Tag`: 태그 타입 (name, url, slug 포함) +- `TagsData`: 태그 목록 응답 타입 (배열 형태) + +### 3. api/index.ts - 도메인별 API 함수 분리 + +#### Post API (src/entities/post/api/index.ts) +- `fetchPosts`: 게시물 목록 조회 +- `addPost`: 게시물 추가 +- `deletePost`: 게시물 삭제 +- `searchPosts`: 게시물 검색 + +#### User API (src/entities/user/api/index.ts) +- `fetchUsers`: 사용자 목록 조회 + +#### Comment API (src/entities/comment/api/index.ts) +- `fetchComments`: 댓글 목록 조회 +- `addComment`: 댓글 추가 +- `updateComment`: 댓글 수정 +- `deleteComment`: 댓글 삭제 +- `fetchCommentsByPostId`: 특정 게시물의 댓글 조회 + +#### Tag API (src/entities/tag/api/index.ts) +- `fetchTags`: 태그 목록 조회 + +### 4. index.ts - Public API Export + +각 entities의 index.ts에서 model, api를 재export하여 외부에서 사용할 수 있도록 구성 + +```typescript +// src/entities/post/index.ts 예시 +export * from "./model/types" +export * from "./api" +``` + +### 5. PostsManagerPage.tsx import 경로 수정 + +기존 경로에서 새로운 entities 경로로 변경: +```typescript +// Before +import { Post, User, Comment } from "@/shared/types" + +// After +import { Post, PostsData, NewPost } from "@/entities/post" +import { User, UsersData } from "@/entities/user" +import { Comment, CommentsData, NewComment } from "@/entities/comment" +import { Tag } from "@/entities/tag" +``` + +## 생성/수정된 파일 목록 + +### 새로 생성된 파일 +- `src/entities/post/model/types.ts` +- `src/entities/post/api/index.ts` +- `src/entities/post/index.ts` +- `src/entities/user/model/types.ts` +- `src/entities/user/api/index.ts` +- `src/entities/user/index.ts` +- `src/entities/comment/model/types.ts` +- `src/entities/comment/api/index.ts` +- `src/entities/comment/index.ts` +- `src/entities/tag/model/types.ts` +- `src/entities/tag/api/index.ts` +- `src/entities/tag/index.ts` + +### 수정된 파일 +- `src/pages/PostsManagerPage.tsx` - import 경로 변경 +- `src/shared/api/types.ts` - Data 타입 유지 (공통 응답 타입) + +## 주요 결정 사항 + +### 1. Data 타입 위치: shared/api/types.ts +- **이유**: 모든 도메인의 응답 구조에 공통으로 사용되는 타입 +- **장점**: DRY 원칙 준수, 응답 구조 변경 시 한 곳에서 관리 +- **FSD 준수**: shared 레이어는 공통 타입/유틸 제공 목적에 부합 + +### 2. PostWithAuthor 타입 제거 결정 +- **현재 상태**: 아직 정의하지 않음 +- **이유**: features 레이어에서 Post와 User를 조합하여 사용할 예정 +- **위치**: 필요 시 features/posts/model 또는 widgets 레이어에서 정의 + +### 3. API 함수의 순수성 유지 +- **entities/api**: 순수 HTTP 요청/응답만 처리 +- **features/api**: 상태 업데이트, 에러 핸들링, 비즈니스 로직 포함 +- **분리 기준**: entities는 데이터 계층, features는 동작 계층 + +### 4. ui segment는 Task 4 이후 처리 +- **현재**: api, model만 분리 완료 +- **계획**: features 레이어 구성 후 표현 컴포넌트 분리 +- **순서**: features → widgets → entities/ui 순으로 정리 + +## FSD 의존성 방향 검증 + +``` +pages → features → entities → shared + ↓ ↓ ↓ ↓ + (X) (X) (O) (O) +``` + +- entities는 shared만 의존 +- entities 간 상호 의존 없음 +- PostsManagerPage는 entities를 직접 import (Task 4에서 features로 전환 예정) + +## 다음 단계 (Task 4) + +1. features 레이어 구성 + - 사용자 행동(이벤트 처리) 분리 + - feature별 ui 분리 + - feature별 api 분리 (entities/api 활용) + +2. PostsManagerPage 리팩토링 + - features로 로직 이동 + - 상태 관리 및 이벤트 핸들러 분리 + +## 배운 점 + +1. **타입 설계의 중요성**: 공통 타입(Data)과 도메인 타입을 분리하여 재사용성 향상 +2. **API 계층 분리**: entities는 데이터 접근만, features는 비즈니스 로직 처리 +3. **FSD 아키텍처 실전 적용**: segment(model, api, ui) 구조로 명확한 책임 분리 +4. **점진적 리팩토링**: ui는 나중에 정리하고 먼저 model, api 분리로 진행 + +## 특이사항 + +- Comment 타입의 중첩된 user 정보는 API 응답 구조 그대로 유지 +- 태그별 게시물 조회 등 복합 기능은 features 레이어에서 처리 예정 +- updatePost는 주석 처리 (features 레이어에서 재구현 예정) diff --git a/.claude/state/progress.json b/.claude/state/progress.json new file mode 100644 index 000000000..934706c3a --- /dev/null +++ b/.claude/state/progress.json @@ -0,0 +1,13 @@ +{ + "startedAt": "2025-12-08T00:00:00Z", + "currentTask": 10, + "totalTasks": 10, + "completedTasks": 10, + "hintsUsed": { + "level1": 10, + "level2": 1, + "level3": 0, + "level4": 1 + }, + "lastUpdated": "2025-12-12T23:59:00Z" +} diff --git a/.claude/state/tasks.md b/.claude/state/tasks.md new file mode 100644 index 000000000..016fe45a5 --- /dev/null +++ b/.claude/state/tasks.md @@ -0,0 +1,41 @@ +# 📋 태스크 목록 + +## 현재 진행 중 + +없음 + +## 대기 중 + +- [ ] **Task 11**: 최종 검토 및 정리 (선택) + - FSD 의존성 방향 검증 + - 폴더 구조 최종 정리 + - 테스트 실행 확인 + +## 완료 (기본과제) + +- [x] **Task 1**: 프로젝트 구조 분석 및 문제점 파악 +- [x] **Task 2**: shared 레이어 구성 +- [x] **Task 3**: entities 레이어 구성 +- [x] **Task 4**: features 레이어 구성 +- [x] **Task 5**: 전역상태관리 적용 (Jotai) +- [x] **Task 6**: widgets 레이어 구성 +- [x] **Task 7**: pages 레이어 구성 +- [x] **Task 8**: app 레이어 구성 + +## 완료 (심화과제) + +- [x] **Task 9**: 최종 검토 및 정리 +- [x] **Task 10**: TanStack Query 마이그레이션 + - TanStack Query 설치 및 Provider 설정 + - API 호출을 useQuery/useMutation으로 대체 + - 쿼리 키 설계 및 적용 + - 캐싱 전략 구현 (setQueryData로 캐시 직접 업데이트) + - Mock API 대응 (DummyJSON) + - 프로덕션 배포 설정 (API base URL) + - Devtools 설정 + +--- + +**총 태스크**: 10개 +**완료**: 10개 +**진행률**: 100% diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index aeb47f408..7b9374ad3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,13 @@ +# 제출 링크 + +- https://daehyunk1m.github.io/front_7th_chapter3-3/ + ## 과제 체크포인트 ### 기본과제 #### 목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기 + - 전역상태관리를 사용해서 상태를 분리하고 관리하는 방법에 대한 이해 - Context API, Jotai, Zustand 등 상태관리 라이브러리 사용하기 - FSD(Feature-Sliced Design)에 대한 이해 @@ -12,22 +17,22 @@ - 어디에 무엇을 넣어야 하는가? #### 체크포인트 -- [ ] 전역상태관리를 사용해서 상태를 분리하고 관리했나요? -- [ ] Props Drilling을 최소화했나요? -- [ ] shared 공통 컴포넌트를 분리했나요? -- [ ] shared 공통 로직을 분리했나요? -- [ ] entities를 중심으로 type을 정의하고 model을 분리했나요? -- [ ] entities를 중심으로 ui를 분리했나요? -- [ ] entities를 중심으로 api를 분리했나요? -- [ ] feature를 중심으로 사용자행동(이벤트 처리)를 분리했나요? -- [ ] feature를 중심으로 ui를 분리했나요? -- [ ] feature를 중심으로 api를 분리했나요? -- [ ] widget을 중심으로 데이터를 재사용가능한 형태로 분리했나요? +- [x] 전역상태관리를 사용해서 상태를 분리하고 관리했나요? +- [x] Props Drilling을 최소화했나요? +- [x] shared 공통 컴포넌트를 분리했나요? +- [x] shared 공통 로직을 분리했나요? +- [x] entities를 중심으로 type을 정의하고 model을 분리했나요? +- [x] entities를 중심으로 ui를 분리했나요? +- [x] entities를 중심으로 api를 분리했나요? +- [x] feature를 중심으로 사용자행동(이벤트 처리)를 분리했나요? +- [x] feature를 중심으로 ui를 분리했나요? +- [x] feature를 중심으로 api를 분리했나요? +- [x] widget을 중심으로 데이터를 재사용가능한 형태로 분리했나요? ### 심화과제 -#### 목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기 +#### 목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기 - TanstackQuery의 사용법에 대한 이해 - TanstackQuery를 이용한 비동기 코드 작성에 대한 이해 @@ -35,31 +40,145 @@ #### 체크포인트 -- [ ] 모든 API 호출이 TanStack Query의 useQuery와 useMutation으로 대체되었는가? -- [ ] 쿼리 키가 적절히 설정되었는가? -- [ ] fetch와 useState가 아닌 선언적인 함수형 프로그래밍이 적절히 적용되었는가? -- [ ] 캐싱과 리프레시 전략이 올바르게 구현되었는가? -- [ ] 낙관적인 업데이트가 적용되었는가? -- [ ] 에러 핸들링이 적절히 구현되었는가? -- [ ] 서버 상태와 클라이언트 상태가 명확히 분리되었는가? -- [ ] 코드가 간결하고 유지보수가 용이한 구조로 작성되었는가? -- [ ] TanStack Query의 Devtools가 정상적으로 작동하는가? - +- [x] 모든 API 호출이 TanStack Query의 useQuery와 useMutation으로 대체되었는가? +- [x] 쿼리 키가 적절히 설정되었는가? +- [x] fetch와 useState가 아닌 선언적인 함수형 프로그래밍이 적절히 적용되었는가? +- [x] 캐싱과 리프레시 전략이 올바르게 구현되었는가? +- [x] 낙관적인 업데이트가 적용되었는가? +- [x] 에러 핸들링이 적절히 구현되었는가? +- [x] 서버 상태와 클라이언트 상태가 명확히 분리되었는가? +- [x] 코드가 간결하고 유지보수가 용이한 구조로 작성되었는가? +- [x] TanStack Query의 Devtools가 정상적으로 작동하는가? ### 최종과제 -- [ ] 폴더구조와 나의 멘탈모데일이 일치하나요? -- [ ] 다른 사람이 봐도 이해하기 쉬운 구조인가요? + +- [x] 폴더구조와 나의 멘탈모데일이 일치하나요? +- [x] 다른 사람이 봐도 이해하기 쉬운 구조인가요? ## 과제 셀프회고 ++12-8 +에이전트 페어 프로그래밍을 위한 문서 세팅과 에이전트 세팅 +클로드와 함께 에이전트 세팅과 프롬프트 문서를 다듬었더니 v5에 와선 나름 괜찮은 시스템이 된 것 같은데, +지난 주차와 같은 환경에선 거의 제대로 동작하지 않았다. 과제의 유형 케이스에 따라 성능이 갈릴 것으로 추측. + +태스크 매니저 에이전트 사용시 결과물 + +```plaintext +좋은 질문들이에요! 하나씩 힌트를 드릴게요. + + --- + 💡 힌트 + + 1. DialogHeader 타입 + + DialogHeader는
요소를 반환하고 있어요. 어떤 HTML + 요소의 속성을 상속받아야 할까요? + + 힌트: DialogContentProps가 HTMLDivElement를 사용한 것처럼, + 동일한 패턴을 적용해보세요. + + --- + 2. highlightText 함수 위치 + + JSX를 반환하는 함수의 위치에 대한 고민이시네요. + + 질문을 던져볼게요: + - highlightText는 어떤 도메인에서 사용되나요? (Post? + Comment? 둘 다?) + - 이 함수가 검색 기능과 관련있다면, 어느 레이어가 + 적절할까요? + + FSD 관점에서 생각해보세요: + - shared/lib → 도메인에 독립적인 순수 유틸리티 + - shared/ui → 도메인에 독립적인 UI 컴포넌트 + - features/search → 검색 기능과 관련된 로직 + + --- + 3. API 함수들 분류 + + 나열하신 함수들을 도메인별로 분류해보세요: + + | 도메인 | 함수 | + |---------|-----| + | Post | ? | + | Comment | ? | + | User | ? | + | Tag | ? | + + 이렇게 분류하면 어느 레이어에 배치해야 할지 힌트가 보일 + 거예요. + + 추가 질문: + - 이 함수들은 shared/api에 가야 할까요, 아니면 + entities/post/api에 가야 할까요? + - FSD의 의존성 방향을 생각해보세요! +``` + ++12-9 +760줄짜리 PostsManagerPage.tsx를 분석했다. 타입 정의, +API 호출, 상태 관리, UI 렌더링이 한 파일에 다 들어있었다. +"이걸 어떻게 나누지?"라는 막막함이 있었는데, FSD +레이어별로 하나씩 분리하면 된다는 걸 깨달았다. + +Button, Card, Dialog 등 공통 UI 컴포넌트를 shared/ui로 분리했다. +DialogHeader 타입을 뭘로 해야 하나 고민했는데, +렌더링하는 요소(div)에 맞춰 HTMLDivElement로 하면 된다는 걸 알았다. +highlightText 함수 위치도 고민이었는데, 검색 기능에 +종속적이니까 features/search로 가는 게 맞았다. + ++12-10 +entities 레이어 구성 완료 +Post, User, Comment, Tag 4개 도메인을 분리했다. +model/types.ts에 타입, api/index.ts에 fetch 함수를 넣었다. +가장 고민했던 건 PostWithAuthor 타입 위치였다. Post + +User 조합인데 entities에 두면 서로 참조하게 돼서 FSD 규칙 위반이었다. +결국 "조합은 상위 레이어에서"라는 원칙으로 features에 두기로 했다. +API 함수도 순수하게 통신만 하고, +상태 업데이트(setPosts 같은)는 features에서 하는 게 맞다는 걸 배웠다. + ++12-11 +FSD 전체 레이어 구성 완료. shared → entities → features → widgets → +pages. +처음엔 "왜 이렇게 나누지?"였는데, 막상 완성하고 보니 +"이 코드 어디 있지?"가 "당연히 저기 있겠지"로 바뀌었다. +의존성 방향이 한쪽으로만 흐르니까 수정할 때 영향 범위가 예측 +가능해졌다. +구조가 곧 문서가 된다는 말을 이해했다. + ### 이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요. +@x 패턴이라는 게 있다는 걸 처음 알았다. +PostWithAuthor처럼 Post + User를 조합한 타입을 어디에 둬야 할지 한참 고민했는데, +entities 간 참조가 필요할 때 @x 폴더로 명시적으로 표현하면 된다는 걸 배웠다. +FSD가 단순히 폴더 규칙이 아니라 "의존성을 어떻게 표현할 것인가"에 대한 철학이라는 게 와닿았다. + ### 본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요? +"왜 이렇게 나눠야 하는가"를 이해하려고 노력했다. +처음엔 FSD 폴더 구조를 그냥 따라 만들면 되는 줄 알았는데, 막상 코드를 옮기다 보니 "이건 shared인가 entities인가?" 같은 판단이 계속 필요했다. + ### 아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요. +각 레이어마다의 경계가 아직 좀 모호하다. +"재사용 가능한 UI 조합"이면 widgets인데, post-filter처럼 기능이 섞인 건 어디로 가야 하는지. +일단은 "사용자 행동 = features, 데이터 표시 조합 = widgets"으로 +구분했는데 맞는지 확신이 없다. + +캐싱 전략도 더 파봐야 할 것 같다. staleTime을 1분으로 설정해놨는데, 실제 서비스에서는 어떤 기준으로 정해야 +하는지 감이 안 온다. + ### 이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요. +다음 프로젝트에서는 처음부터 FSD 구조로 시작해보고 싶다. +이번엔 760줄짜리 컴포넌트를 나중에 쪼갠 거라 삽질이 많았는데, +처음부터 레이어 구분하면서 시작하면 훨씬 수월할 것 같다. + +그리고 TanStack Query + Jotai 조합도 괜찮았다. +서버 상태는 Query로, 클라이언트 상태는 Atom으로 분리하니까 +"이 상태는 어디서 관리하지?"라는 고민이 사라졌다. +낙관적 업데이트도 이번에 처음 적용해봤는데, 사용자 경험 차이가 커서 +앞으로는 CRUD 있는 곳에 기본으로 넣어볼 생각이다. ## 챕터 셀프회고 @@ -68,15 +187,51 @@ > 아래에 적힌 질문들은 추억(?)을 회상할 수 있도록 도와주려고 만든 질문이며, 꼭 질문에 대한 대답이 아니어도 좋으니 내가 느꼈던 인사이트들을 자유롭게 적어주세요. ### 클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기 + - 더티코드를 접했을 때 어떤 기분이었나요? ^^; 클린코드의 중요성, 읽기 좋은 코드란 무엇인지, 유지보수하기 쉬운 코드란 무엇인지에 대한 생각을 공유해주세요 +1333줄짜리 단일 파일에 장바구니 계산 로직과 쿠폰 적용, 상품 관리, UI 상태가 전부 한 곳에 뒤섞여 있었습니다. "이 코드에서 할인 로직만 수정하려면 어디를 봐야하지?" 싶은 막막함이 있었습니다. + +유지보수하기 쉬운 코드란 결국 변경의 영향 범위가 예측 가능한 코드라고 생각합니다. 1333줄짜리 파일에서는 한 줄 고치면 어디에 영향을 줄지 몰랐는데, 역할별로 분리하고 나니 "이건 여기만 고치면 되겠구나"가 명확해졌습니다. + ### 결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리 + - 거대한 단일 컴포넌트를 봤을때의 느낌! 처음엔 막막했던 상태관리, 디자인 패턴이라는 말이 어렵게만 느껴졌던 시절, 순수함수로 분리하면서 "아하!"했던 순간, 컴포넌트가 독립적이 되어가는 과정에서의 깨달음을 들려주세요 +디자인 패턴", "순수함수"라는 말은 알지만 이 엉킨 코드에 어떻게 적용할지 감이 안 왔습니다. + +"아하!" 모먼트는 순수함수를 분리했을 때 다가왔습니다. calculateItemTotal은 CartItem을 받아서 가격을 리턴하면 끝이었습니다. +useState도 필요 없고, 컴포넌트도 몰라도 됩니다. 이렇게 계산 로직을 models/ 폴더에 빼놓으니 "할인 로직 수정? 여기만 보면 되겠구나"가 명확해졌습니다. +결국 "결합도를 낮춘다"는 건 관심사를 분리하는 단순한 원칙을 지키는것이라는 걸 깨달았습니다. + ### 응집도 높이기: 서버상태관리, 폴더 구조 + - "이 코드는 대체 어디에 둬야 하지?"라고 고민했던 시간, FSD를 적용해보면서의 느낌, 나만의 구조를 만들어가는 과정, TanStack Query로 서버 상태를 분리하면서 느낀 해방감(?)등을 공유해주세요 +앞으로 새 프로젝트 시작할 때 FSD 기반으로 폴더 구조 잡을 것 같습니다. +"이 코드 어디 있지?"가 "당연히 저기 있겠지"로 바뀌는 경험이 좋았습니다. 앞으론 새로운 팀원이 합류해도 구조만 보면 바로 이해할 수 있을 것 같다라는 확신이 들었습니다. +TanStack Query를 쓰는 경험도 좋았습니다. +useState + useEffect로 fetch하면 의도치 않은 사이드이펙트를 잡느랴 오래걸렸는데 선언적으로 "이 데이터가 필요해"만 적으면 캐싱, 리프레시, 에러 핸들링이 다 따라오니까 좋았습니다. ## 리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 -``` \ No newline at end of file + +``` +@x 패턴 사용이 적절했는지 궁금합니다 + +--- + +export interface PostWithAuthor extends Post { + author?: User +} + +Post와 User의 합성 타입인 PostWithAuthor 타입의 위치가 고민됐었습니다. + +엔티티는 최대한 순수하게 두려고 최소 피쳐 레이어 이상에서 합성하려고 했는데 PostWithAuthor가 여러 feature에서 사용하는 타입이라 어디다가 위치해야할지 헷갈렸습니다. + +문서를 찾아보니 비즈니스 엔티티 간의 상호 참조 패턴 중에 Cross-import(@x)를 사용하는 방법(참조: https://feature-sliced.design/kr/docs/guides/examples/types#%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4entity%EC%99%80-%EC%83%81%ED%98%B8-%EC%B0%B8%EC%A1%B0)이 있었고. + +최종적으론 `entities/post/@x/with-user.ts`에 뒀는데, 개인적으로 `as unknown as User` 같은 느낌이라 영 맘에 들진 않았습니다. 그냥 features 레이어에서 정의하는 게 더 나았을까요? + +코치님의 상황에선 어떻게 접근하셨을지 궁금합니다. +``` diff --git a/.gitignore b/.gitignore index a547bf36d..978774c86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Logs -logs *.log +# logs 폴더는 무시하되 .claude/state/logs는 유지 +!/logs +!.claude/state/logs/ npm-debug.log* yarn-debug.log* yarn-error.log* @@ -11,6 +13,7 @@ node_modules dist dist-ssr *.local +*.tsbuildinfo # Editor directories and files .vscode/* diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 000000000..b5b11fda1 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,23 @@ +{ + "mcpServers": { + "playwright": { + "command": "npx", + "args": ["-y", "@executeautomation/playwright-mcp-server"] + }, + "context7": { + "command": "npx", + "args": ["-y", "@upstash/context7-mcp@latest"] + }, + "sequential-thinking": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"] + }, + "storybook": { + "command": "npx", + "args": ["-y", "@storybook/mcp@latest"], + "env": { + "STORYBOOK_URL": "http://localhost:6006" + } + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..a97c8f98f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,140 @@ +# CLAUDE.md + +## 📖 이 프로젝트는 + +학습 과제 수행을 위한 프로젝트입니다. +Claude는 **직접 코드를 작성해주는 것이 아니라**, +학습을 돕고 힌트를 제공하는 역할입니다. + +## Response Guidelines + +- 항상 한글로 대답할 것 +- 사용자의 질문에 답할 때 MCP를 사용하면 더 좋은 대답이 나올 경우 반드시 MCP를 사용할 것 + +## 🎯 학습 정보 + +- **목표**: `.claude/state/learning.md` 참조 +- **태스크**: `.claude/state/tasks.md` 참조 +- **진행 상황**: `.claude/state/progress.json` 참조 +- **과제 체크포인트**: `.github/pull_request_template.md` 참조 + +## ⚡ 명령어 + +| 명령어 | 설명 | +| ----------------- | ---------------- | +| `/start` | 세션 시작 | +| `/end` | 세션 종료 | +| `/setup` | 초기 설정 | +| `/hint` | 힌트 요청 | +| `/check` | 과제 검증 | +| `/done` | 태스크 완료 | +| `/commit` | 커밋 메시지 생성 | + +## 🤖 에이전트 + +| 에이전트 | 역할 | +| ------------ | ----------------------- | +| task-manager | 태스크 관리, 로깅, 커밋 | +| guide | 힌트 제공, 개념 설명 | +| analyzer | 코드 분석, 구조 설명 | +| checker | 과제 검증, 피드백 | + +## 📝 커밋 규칙 + +``` +Type: 내용 + +- 세부 내용 +- 세부 내용 +``` + +- Type: Feat, Fix, Refactor, Style, Docs, Test, Chore +- **Type은 영어 대문자로 시작** +- **내용은 한글로 작성** + +## ⛔ 핵심 원칙 + +### 절대 하지 않을 것 +- 정답 코드를 바로 제공 +- 구현을 대신 해주기 +- 로그 없이 태스크 완료 + +### 항상 할 것 +- `/start`로 세션 시작, `/end`로 세션 종료 +- 단계적 힌트 제공 (Level 1→2→3→4) +- 스스로 해결하도록 유도 +- 태스크 완료 시 로그 작성 +- 세션 종료 시 세션 로그 업데이트 +- 커밋 메시지 형식 준수 + +--- + +## Project Overview + +This is a React 19 posts management application built with TypeScript, Vite, and styled with Tailwind CSS. It communicates with the DummyJSON API through a Vite proxy and allows CRUD operations for posts and comments. + +## 🛠️ Tech Stack + +- React 19 +- TypeScript +- Vite +- Tailwind CSS +- Radix UI +- React Router +- Vitest + +## Commands + +```bash +# Development +pnpm dev # Start dev server with HMR +pnpm build # Type-check with tsc then build +pnpm lint # Run ESLint +pnpm preview # Preview production build + +# Testing +pnpm test # Run vitest in watch mode +pnpm coverage # Run tests with coverage report +``` + +## Architecture + +### Entry Points +- `src/main.tsx` - Application bootstrap +- `src/index.tsx` - Alternative entry point (exports app with router wrapper) +- `src/App.tsx` - Root component with layout (Header, Footer) and routes + +### Source Structure +``` +src/ +├── components/ +│ ├── index.tsx # UI primitives (Button, Card, Dialog, Select, Table, Input, Textarea) +│ ├── Header.tsx # Site header with navigation +│ └── Footer.tsx # Site footer +└── pages/ + └── PostsManagerPage.tsx # Main page with all post/comment management logic +``` + +### Key Patterns + +**UI Components (`src/components/index.tsx`):** +- Built on Radix UI primitives (Dialog, Select) +- Styled with `class-variance-authority` for variant handling +- All components use `forwardRef` pattern for ref forwarding + +**API Integration:** +- All API calls go through `/api/*` proxy (configured in `vite.config.ts`) +- Proxy rewrites `/api` to DummyJSON API (`https://dummyjson.com`) +- Endpoints: `/posts`, `/users`, `/comments`, `/posts/tags` + +**State Management:** +- URL state synced with React Router's `useNavigate` and `useLocation` +- Query params: `skip`, `limit`, `search`, `sortBy`, `sortOrder`, `tag` +- Local state for dialogs, selected items, and API data + +## Testing Configuration + +- Framework: Vitest with jsdom environment +- Globals enabled (`test.globals: true`) +- React Testing Library available for component tests +- MSW available for API mocking diff --git a/Init-prompt.md b/Init-prompt.md new file mode 100644 index 000000000..2cc5d6155 --- /dev/null +++ b/Init-prompt.md @@ -0,0 +1,157 @@ +# Claude Code 학습 과제 초기화 프롬프트 v5 + +> **사용법**: 아래 프롬프트에서 `[입력]` 부분만 채워서 사용 +> **원리**: 상세 지시사항은 모두 SETTING.md에 있음 + +--- + +## 🚀 기본 프롬프트 + +``` +프로젝트 루트의 SETTING.md를 읽고 "초기화 수행 단계" 섹션을 따라 설정해줘. + +## 내 학습 정보 + +**학습 목표**: +[이 과제를 통해 배우려는 것] + +**유의점**: +[주의사항, 제약조건] + +**참고 자료**: +- 소스코드: [경로] +- 문서: [경로 또는 링크] +- 외부 링크: [URL] + +**과제 내용**: +[구체적으로 해야 할 것] +``` + +--- + +## 📚 예시: React 과제 + +``` +프로젝트 루트의 SETTING.md를 읽고 "초기화 수행 단계" 섹션을 따라 설정해줘. + +## 내 학습 정보 + +**학습 목표**: +React의 상태 관리와 이벤트 핸들링 이해하기 + +**유의점**: +- 외부 라이브러리 사용 금지 +- 함수형 컴포넌트만 사용 +- TypeScript 타입 정의 필수 + +**참고 자료**: +- 소스코드: src/components/TodoList.tsx +- 문서: docs/requirements.md +- 외부 링크: https://react.dev/learn/state-a-components-memory + +**과제 내용**: +TodoList 컴포넌트에서 할 일 추가/삭제/완료 토글 기능 구현 +``` + +--- + +## 📚 예시: 항해99 과제 + +``` +프로젝트 루트의 SETTING.md를 읽고 "초기화 수행 단계" 섹션을 따라 설정해줘. + +## 내 학습 정보 + +**학습 목표**: +JavaScript 클로저와 스코프 체인 이해하기 + +**유의점**: +- ES6+ 문법 사용 +- 주석으로 동작 원리 설명 필수 +- 테스트 코드 통과해야 함 + +**참고 자료**: +- 소스코드: src/closure.js, src/scope.js +- 문서: docs/week3-guide.md +- 외부 링크: https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures + +**과제 내용**: +1. createCounter 함수 구현 (클로저 활용) +2. privateVariable 패턴 구현 +3. 이벤트 핸들러에서 클로저 활용 +``` + +--- + +## 🔄 기존 프로젝트에 추가 + +``` +프로젝트 루트의 SETTING.md를 읽고 "초기화 수행 단계" 섹션을 따라 설정해줘. + +## 현재 상태 +- 완료: [이미 한 것] +- 진행 중: [현재 하는 것] +- 남은 것: [앞으로 할 것] + +## 내 학습 정보 + +**학습 목표**: +[목표] + +**유의점**: +[유의점] + +**참고 자료**: +- [자료] + +**과제 내용**: +[과제] + +--- + +⚠️ 기존 파일은 건드리지 말고 .claude/ 폴더만 추가해줘. +tasks.md에는 현재 상태를 반영해서 완료/진행중/대기 구분해줘. +``` + +--- + +## ⚡ 최소 버전 + +``` +프로젝트 루트의 SETTING.md를 읽고 "초기화 수행 단계" 섹션을 따라 설정해줘. + +## 내 학습 정보 + +**학습 목표**: [한 줄] +**과제 내용**: [한 줄] +``` + +--- + +## 🔍 설정 확인 (재시작 후) + +``` +SETTING.md의 폴더 구조대로 설정됐는지 확인해줘. + +확인할 것: +1. /agents 명령어에 4개 에이전트 보이는지 +2. .claude/state/ 파일들 있는지 +3. CLAUDE.md에 커밋 규칙 있는지 +``` + +--- + +## 💡 학습 시작 + +``` +첫 번째 태스크를 시작하려고 해. +.claude/state/tasks.md 확인하고 어떻게 접근하면 좋을지 방향만 알려줘. +``` + +--- + +## 📌 핵심 + +1. **SETTING.md가 모든 것을 담고 있음** +2. **프롬프트는 사용자 입력만** +3. **"초기화 수행 단계"를 따르라고 지시** diff --git a/SETTING.md b/SETTING.md new file mode 100644 index 000000000..5c8ff98fd --- /dev/null +++ b/SETTING.md @@ -0,0 +1,976 @@ +# 🎯 Claude Code 학습 과제 세팅 가이드 v6 + +> **목적**: 학습 목표 달성 + 과제 수행 보조 +> **핵심**: Task Manager 중심의 학습 워크플로우 +> **사용법**: 프롬프트에서 이 파일을 참조하고 "초기화 수행 단계"를 따르도록 지시 + +--- + +## 📋 목차 + +1. [이 시스템의 목적](#이-시스템의-목적) +2. [폴더 구조](#폴더-구조) +3. [에이전트 정의](#에이전트-정의) +4. [슬래시 커맨드 정의](#슬래시-커맨드-정의) +5. [상태 파일 정의](#상태-파일-정의) +6. [CLAUDE.md 템플릿](#claudemd-템플릿) +7. [커밋 규칙](#커밋-규칙) +8. [초기화 수행 단계](#초기화-수행-단계) +9. [완료 안내 템플릿](#완료-안내-템플릿) + +--- + +## 🎯 이 시스템의 목적 + +### 이 시스템은 + +``` +✅ 학습 목표와 과제를 체계적으로 관리 +✅ 기존 소스코드 분석 및 이해 도움 +✅ 힌트와 가이드 제공 (직접 해결 유도) +✅ 진행 상황 추적 및 로깅 +✅ 커밋 관리 +``` + +### 이 시스템은 아님 + +``` +❌ 코드를 대신 작성해주는 시스템 +❌ 처음부터 앱을 만드는 개발 도구 +❌ 정답을 바로 알려주는 시스템 +``` + +### 핵심 원칙 + +``` +1. 학습자가 직접 해결하도록 유도 +2. 막힐 때만 힌트 제공 (단계적으로) +3. 과제 완료 시 검증 및 피드백 +4. 모든 진행 상황 기록 +``` + +--- + +## 🏗️ 폴더 구조 + +``` +프로젝트/ +│ +├── CLAUDE.md # 메인 설정 +├── SETTING.md # 이 파일 +│ +└── .claude/ + ├── agents/ # 🤖 에이전트 (4개) + │ ├── task-manager.md + │ ├── guide.md + │ ├── analyzer.md + │ └── checker.md + │ + ├── commands/ # ⚡ 슬래시 커맨드 (7개) + │ ├── start.md # 세션 시작 + │ ├── end.md # 세션 종료 + │ ├── setup.md + │ ├── hint.md + │ ├── check.md + │ ├── done.md + │ └── commit.md + │ + └── state/ # 📍 상태 관리 + ├── learning.md # 학습 목표 & 유의점 + ├── tasks.md # 태스크 목록 + ├── progress.json # 진행 상황 + └── logs/ # 로그 폴더 + ├── task-[n].md # 태스크별 완료 로그 + └── session-YYYY-MM-DD.md # 일일 세션 로그 +``` + +--- + +## 🤖 에이전트 정의 + +### 1. task-manager.md + +```markdown +--- +name: task-manager +description: 학습 태스크 관리를 담당합니다. 태스크 시작, 완료, 진행 상황 확인, 커밋 안내가 필요할 때 호출됩니다. +tools: Read, Write, Edit, Bash, Glob, Grep +--- + +당신은 학습 태스크 매니저입니다. + +## 역할 + +- 태스크 목록 관리 (`.claude/state/tasks.md`) +- 진행 상황 추적 (`.claude/state/progress.json`) +- 로그 작성 (`.claude/state/logs/`) +- 세션 관리 +- 커밋 안내 + +## 세션 시작 시 (/start) + +1. 오늘 날짜의 세션 로그 확인/생성: `.claude/state/logs/session-YYYY-MM-DD.md` +2. `.claude/state/progress.json`에서 현재 상태 확인 +3. `.claude/state/tasks.md`에서 현재 태스크 확인 +4. 이전 세션의 "다음 세션에서 할 일" 확인 +5. 세션 로그에 시작 시간 기록 +6. 사용자에게 현재 상황 안내 + +## 세션 종료 시 (/end) + +1. 오늘 진행한 내용 요약 +2. 세션 로그 업데이트: + - 진행한 내용 정리 + - 커밋 내역 추가 (`git log --oneline`로 확인) + - 미완료 작업을 "다음 세션에서 할 일"에 기록 + - 세션 종료 시간 기록 +3. `.claude/state/progress.json` lastUpdated 갱신 +4. 커밋되지 않은 변경사항 확인 및 제안 +5. 사용자에게 요약 안내 + +## 태스크 완료 시 (/done) + +1. `.claude/state/logs/task-[n].md` 작성 +2. `.claude/state/tasks.md` 업데이트 (체크 표시) +3. `.claude/state/progress.json` 업데이트 +4. 세션 로그에도 완료 내용 기록 +5. 커밋 메시지 제안: +``` + +Type: 내용 + +- 세부 내용 +- 세부 내용 + +``` +6. 다음 태스크 안내 + +## 힌트 사용 시 (/hint) + +1. `.claude/state/progress.json`의 hintsUsed 업데이트 +2. 세션 로그에 힌트 사용 기록 + +## 커밋 시 (/commit) + +1. 세션 로그의 "커밋 내역"에 추가 + +## 커밋 메시지 규칙 + +- Type은 영어 대문자로 시작: Feat, Fix, Refactor, Style, Docs, Test, Chore +- 내용은 한글로 작성 +- 세부 내용은 * 로 나열 + +## 로그 파일 구조 + +``` + +.claude/state/logs/ +├── task-1.md # 완료된 태스크 로그 +├── task-2.md # 완료된 태스크 로그 +├── session-2025-12-08.md # 일일 세션 로그 +└── session-2025-12-09.md # 일일 세션 로그 + +``` + +## 금지사항 + +- 코드 직접 작성 금지 (guide에게 위임) +- 로그 없이 태스크 완료 처리 금지 +- 세션 로그 없이 세션 종료 금지 +``` + +### 2. guide.md + +```markdown +--- +name: guide +description: 학습 가이드와 힌트를 제공합니다. 막혔을 때, 개념 설명이 필요할 때, 접근 방법을 모를 때 호출됩니다. +tools: Read, Grep, Glob, WebSearch, WebFetch +--- + +당신은 학습 가이드입니다. + +## 역할 + +- 개념 설명 +- 단계적 힌트 제공 +- 접근 방법 제안 +- 참고 자료 안내 + +## 힌트 제공 원칙 + +### ⛔ 절대 금지 + +- 정답 코드 전체를 바로 제공 +- 구현을 대신 해주기 +- 복사-붙여넣기만 하면 되는 코드 제공 + +### ✅ 해야 할 것 + +- 단계적 힌트 (Level 1 → 2 → 3 → 4) +- 스스로 생각하게 유도하는 질문 +- 관련 개념 설명 +- 공식 문서 참조 안내 + +## 힌트 레벨 + +**Level 1** (방향성): +"이 문제는 [개념]을 활용하면 됩니다" + +**Level 2** (구체적 방향): +"[개념]을 사용해서 [구체적 접근]을 해보세요" + +**Level 3** (코드 스니펫): +"[핵심 코드 패턴] 형태로 시작해보세요" + +**Level 4** (거의 정답, 최후의 수단): +"[구체적 코드]를 추가하면 됩니다" + +## 힌트 요청 시 응답 형식 +``` + +💡 힌트 (Level [N]) + +[힌트 내용] + +--- + +더 구체적인 힌트가 필요하면 말씀해주세요. + +``` + +``` + +### 3. analyzer.md + +```markdown +--- +name: analyzer +description: 기존 코드를 분석하고 설명합니다. 코드 구조 파악, 함수 동작 이해, 파일 관계 파악이 필요할 때 호출됩니다. +tools: Read, Grep, Glob +--- + +당신은 코드 분석가입니다. + +## 역할 + +- 소스코드 구조 분석 +- 함수/컴포넌트 동작 설명 +- 파일 간 관계 설명 +- 데이터 흐름 파악 + +## 분석 시 포함할 내용 + +### 파일 분석 + +- 파일의 목적 +- 주요 export +- 의존성 (import) +- 다른 파일과의 관계 + +### 함수/컴포넌트 분석 + +- 역할/목적 +- 파라미터 설명 +- 반환값 +- 사용 예시 +- 관련 함수 + +## 응답 형식 +``` + +📂 [파일명] 분석 + +## 목적 + +[이 파일이 하는 일] + +## 구조 + +[주요 구성 요소] + +## 핵심 코드 + +[중요한 부분 설명] + +## 관련 파일 + +- [연관 파일들] + +``` + +## 원칙 +- 설명만 하고 수정 제안은 하지 않음 +- 학습자가 이해할 수 있는 수준으로 설명 +``` + +### 4. checker.md + +```markdown +--- +name: checker +description: 과제 구현을 검증하고 피드백을 제공합니다. 구현 확인, 테스트, 요구사항 충족 여부 확인이 필요할 때 호출됩니다. +tools: Read, Grep, Glob, Bash +--- + +당신은 과제 검증자입니다. + +## 역할 + +- 구현 결과 검증 +- 요구사항 충족 확인 +- 코드 품질 피드백 +- 개선점 제안 + +## 검증 체크리스트 + +1. [ ] 요구사항 충족 여부 +2. [ ] 코드 동작 여부 +3. [ ] 에러 없음 +4. [ ] 유의점 준수 여부 + +## 응답 형식 + +### 통과 시 +``` + +✅ 검증 통과! + +## 잘한 점 + +- [잘한 것 1] +- [잘한 것 2] + +## 개선 제안 (선택) + +- [더 좋게 할 수 있는 점] + +➡️ /project:done 으로 태스크를 완료하세요. + +``` + +### 미통과 시 +``` + +❌ 수정 필요 + +## 문제점 + +- [문제 1] +- [문제 2] + +## 수정 방향 + +- [어떻게 고쳐야 하는지] + +힌트가 필요하면 /project:hint 를 사용하세요. + +``` + +``` + +--- + +## ⚡ 슬래시 커맨드 정의 + +### 1. start.md + +```markdown +--- +description: 학습 세션을 시작합니다 +--- + +학습 세션을 시작합니다. + +## 수행할 작업 + +1. **세션 로그 생성/확인**: `.claude/state/logs/session-YYYY-MM-DD.md` + - 오늘 날짜의 세션 로그가 없으면 새로 생성 + - 이미 있으면 이어서 작성 (재시작 기록) + +2. **현재 상태 확인**: + - `.claude/state/progress.json` 읽기 + - `.claude/state/tasks.md`에서 현재 태스크 확인 + - 이전 세션 로그가 있다면 마지막 진행 상황 확인 + +3. **세션 로그에 기록**: + - 세션 시작 시간 + - 현재 진행 중인 태스크 + - 이전 세션에서 남긴 "다음에 할 일" 확인 + +4. **사용자에게 안내**: + - 현재 태스크 요약 + - 이전 세션에서 미완료된 작업 + - 오늘 할 일 제안 + +$ARGUMENTS +``` + +### 2. end.md + +```markdown +--- +description: 학습 세션을 종료합니다 +--- + +학습 세션을 종료합니다. + +## 수행할 작업 + +1. **오늘 진행 내용 요약**: + - 현재 태스크 진행 상황 파악 + - 완료한 작업 목록 정리 + - 미완료 작업 목록 정리 + +2. **세션 로그 업데이트**: `.claude/state/logs/session-YYYY-MM-DD.md` + - 세션 종료 시간 기록 + - 오늘 진행한 내용 정리 + - 커밋 내역 추가 (git log로 확인) + - "다음 세션에서 할 일" 작성 + +3. **progress.json 업데이트**: + - lastUpdated 시간 갱신 + +4. **커밋 제안** (선택): + - 커밋되지 않은 변경사항이 있으면 커밋 제안 + +5. **사용자에게 안내**: + - 오늘 진행 요약 + - 다음에 이어서 할 내용 + - 수고했다는 인사 + +$ARGUMENTS +``` + +### 3. setup.md + +```markdown +--- +description: 학습 과제 초기 설정을 시작합니다 (최초 1회) +--- + +SETTING.md를 읽고 학습 과제 환경을 설정합니다. + +$ARGUMENTS +``` + +### 4. hint.md + +```markdown +--- +description: 현재 태스크에 대한 힌트를 요청합니다 +--- + +현재 진행 중인 태스크에 대한 힌트를 제공합니다. + +## 힌트 레벨 + +- 기본: Level 1 (방향성 힌트) +- "더 자세히": Level 2 +- "더 구체적으로": Level 3 +- "거의 답": Level 4 + +$ARGUMENTS +``` + +### 5. check.md + +```markdown +--- +description: 과제 구현을 검증합니다 +--- + +현재 구현을 검증하고 피드백을 제공합니다. + +## 검증 항목 + +- 요구사항 충족 +- 코드 동작 여부 +- 유의점 준수 + +$ARGUMENTS +``` + +### 6. done.md + +```markdown +--- +description: 현재 태스크를 완료 처리합니다 +--- + +현재 태스크를 완료하고 다음 작업을 수행합니다: + +1. **로그 작성**: `.claude/state/logs/task-[n].md` +2. **태스크 목록 업데이트**: `.claude/state/tasks.md` +3. **진행 상황 업데이트**: `.claude/state/progress.json` +4. **커밋 메시지 제안** +5. **다음 태스크 안내** + +$ARGUMENTS +``` + +### 7. commit.md + +```markdown +--- +description: 커밋 메시지를 생성합니다 +--- + +현재까지의 변경사항을 기반으로 커밋 메시지를 생성합니다. + +## 커밋 메시지 형식 +``` + +Type: 내용 + +- 세부 내용 +- 세부 내용 + +``` + +## Type 종류 +- Feat: 새로운 기능 +- Fix: 버그 수정 +- Refactor: 리팩토링 +- Style: 스타일 변경 +- Docs: 문서 수정 +- Test: 테스트 +- Chore: 기타 + +## 규칙 +- Type은 영어 대문자로 시작 +- 내용은 한글로 작성 + +$ARGUMENTS +``` + +--- + +## 📍 상태 파일 정의 + +### 1. learning.md 템플릿 + +```markdown +# 📚 학습 정보 + +## 🎯 학습 목표 + +[사용자 입력] + +## ⚠️ 유의점 + +[사용자 입력] + +## 📖 참고 자료 + +### 소스코드 + +- [사용자 입력] + +### 문서 + +- [사용자 입력] + +### 외부 링크 + +- [사용자 입력] + +--- + +**설정일**: [날짜] +``` + +### 2. tasks.md 템플릿 + +```markdown +# 📋 태스크 목록 + +## 현재 진행 중 + +- [ ] **Task 1**: [태스크 설명] + +## 대기 중 + +- [ ] **Task 2**: [태스크 설명] +- [ ] **Task 3**: [태스크 설명] + +## 완료 + +(없음) + +--- + +**총 태스크**: [N]개 +**완료**: 0개 +**진행률**: 0% +``` + +### 3. progress.json 템플릿 + +```json +{ + "startedAt": "[현재 시간]", + "currentTask": 1, + "totalTasks": 0, + "completedTasks": 0, + "hintsUsed": { + "level1": 0, + "level2": 0, + "level3": 0, + "level4": 0 + }, + "lastUpdated": "[현재 시간]" +} +``` + +### 4. logs/session-YYYY-MM-DD.md 템플릿 + +```markdown +# 세션 로그: YYYY-MM-DD + +## 세션 정보 + +- **시작**: YYYY-MM-DD HH:MM +- **종료**: YYYY-MM-DD HH:MM +- **현재 태스크**: Task N (태스크명) +- **상태**: 진행 중 / 완료 + +## 이전 세션 이어서 + +- [이전 미완료 작업들] + +## 오늘 진행한 내용 + +### [작업 내용] + +#### 완료한 작업 +- [x] 작업 1 +- [x] 작업 2 + +#### 미완료 작업 +- [ ] 작업 3 + +#### 고민한 점 +- [고민 내용] + +## 힌트 사용 + +- Level N: [내용] + +## 커밋 내역 + +| 해시 | 메시지 | +|------|--------| +| abc1234 | Feat: 기능 추가 | + +## 다음 세션에서 할 일 + +- [ ] 할 일 1 +- [ ] 할 일 2 + +## 메모 + +- [추가 메모] +``` + +### 5. logs/task-[n].md 템플릿 + +```markdown +# Task [N]: [태스크명] + +## 📋 정보 + +- **시작**: [날짜시간] +- **완료**: [날짜시간] +- **소요 시간**: [시간] +- **힌트 사용**: [횟수] + +## 🎯 목표 + +[이 태스크에서 해야 했던 것] + +## ✅ 완료 내용 + +- [한 일 1] +- [한 일 2] + +## 📁 수정한 파일 + +- `path/to/file.ts` - [변경 내용] + +## 💡 배운 점 + +- [배운 것] + +## ⚠️ 어려웠던 점 + +- [어려웠던 것과 해결 방법] + +## 🔗 커밋 + +- [커밋 메시지] +``` + +--- + +## 📄 CLAUDE.md 템플릿 + +```markdown +# CLAUDE.md + +## 📖 이 프로젝트는 + +학습 과제 수행을 위한 프로젝트입니다. +Claude는 **직접 코드를 작성해주는 것이 아니라**, +학습을 돕고 힌트를 제공하는 역할입니다. + +## 🎯 학습 정보 + +- **목표**: `.claude/state/learning.md` 참조 +- **태스크**: `.claude/state/tasks.md` 참조 +- **진행 상황**: `.claude/state/progress.json` 참조 + +## ⚡ 명령어 + +| 명령어 | 설명 | +| -------- | ---------------- | +| `/start` | 세션 시작 | +| `/end` | 세션 종료 | +| `/setup` | 초기 설정 | +| `/hint` | 힌트 요청 | +| `/check` | 과제 검증 | +| `/done` | 태스크 완료 | +| `/commit`| 커밋 메시지 생성 | + +## 🤖 에이전트 + +| 에이전트 | 역할 | +| ------------ | ----------------------- | +| task-manager | 태스크 관리, 로깅, 커밋 | +| guide | 힌트 제공, 개념 설명 | +| analyzer | 코드 분석, 구조 설명 | +| checker | 과제 검증, 피드백 | + +## 📝 커밋 규칙 +``` + +Type: 내용 + +- 세부 내용 +- 세부 내용 + +``` + +- Type: Feat, Fix, Refactor, Style, Docs, Test, Chore +- **Type은 영어 대문자로 시작** +- **내용은 한글로 작성** + +## ⛔ 핵심 원칙 + +### 절대 하지 않을 것 +- 정답 코드를 바로 제공 +- 구현을 대신 해주기 +- 로그 없이 태스크 완료 + +### 항상 할 것 +- `/start`로 세션 시작, `/end`로 세션 종료 +- 단계적 힌트 제공 (Level 1→2→3→4) +- 스스로 해결하도록 유도 +- 태스크 완료 시 로그 작성 +- 세션 종료 시 세션 로그 업데이트 +- 커밋 메시지 형식 준수 + +## 🛠️ Tech Stack + +[프로젝트별 작성] +``` + +--- + +## 📝 커밋 규칙 + +### 형식 + +``` +Type: 내용 + +* 세부 내용 +* 세부 내용 +``` + +### Type 종류 + +| Type | 용도 | 예시 | +| ---------- | ----------- | -------------------------------- | +| `Feat` | 새로운 기능 | Feat: 로그인 폼 유효성 검사 구현 | +| `Fix` | 버그 수정 | Fix: 버튼 중복 클릭 문제 해결 | +| `Refactor` | 리팩토링 | Refactor: 컴포넌트 구조 개선 | +| `Style` | 스타일 변경 | Style: 버튼 색상 변경 | +| `Docs` | 문서 수정 | Docs: README 업데이트 | +| `Test` | 테스트 | Test: 로그인 테스트 추가 | +| `Chore` | 기타 | Chore: 패키지 업데이트 | + +### 규칙 + +- **Type은 영어 대문자로 시작** +- **내용은 한글로 작성** +- 세부 내용은 `*`로 나열 + +### 예시 + +``` +Feat: 할 일 추가 기능 구현 + +* 입력 폼 컴포넌트 생성 +* 상태 관리 로직 추가 +* 추가 버튼 이벤트 핸들러 구현 +``` + +``` +Fix: 할 일 삭제 시 에러 수정 + +* 배열 인덱스 범위 체크 추가 +* 삭제 후 상태 업데이트 수정 +``` + +--- + +## 🚀 초기화 수행 단계 + +> Claude는 이 단계를 순서대로 수행합니다. + +### Step 1: 폴더 구조 생성 + +```bash +mkdir -p .claude/agents +mkdir -p .claude/commands +mkdir -p .claude/state/logs +``` + +### Step 2: 에이전트 파일 생성 + +`.claude/agents/` 폴더에 4개 파일 생성: + +1. **task-manager.md** - [에이전트 정의 > task-manager.md](#1-task-managermd) 섹션 내용 그대로 복사 +2. **guide.md** - [에이전트 정의 > guide.md](#2-guidemd) 섹션 내용 그대로 복사 +3. **analyzer.md** - [에이전트 정의 > analyzer.md](#3-analyzermd) 섹션 내용 그대로 복사 +4. **checker.md** - [에이전트 정의 > checker.md](#4-checkermd) 섹션 내용 그대로 복사 + +⚠️ **중요**: 각 파일은 반드시 `---`로 시작하는 YAML frontmatter를 포함해야 함 + +### Step 3: 슬래시 커맨드 생성 + +`.claude/commands/` 폴더에 7개 파일 생성: + +1. **start.md** - [슬래시 커맨드 정의](#1-startmd) 섹션 참조 +2. **end.md** - [슬래시 커맨드 정의](#2-endmd) 섹션 참조 +3. **setup.md** - [슬래시 커맨드 정의](#3-setupmd) 섹션 참조 +4. **hint.md** - [슬래시 커맨드 정의](#4-hintmd) 섹션 참조 +5. **check.md** - [슬래시 커맨드 정의](#5-checkmd) 섹션 참조 +6. **done.md** - [슬래시 커맨드 정의](#6-donemd) 섹션 참조 +7. **commit.md** - [슬래시 커맨드 정의](#7-commitmd) 섹션 참조 + +### Step 4: 상태 파일 생성 + +`.claude/state/` 폴더에 3개 파일 생성: + +1. **learning.md** - 사용자가 제공한 학습 목표, 유의점, 참고 자료로 채움 +2. **tasks.md** - 사용자가 제공한 과제 내용을 태스크로 분해해서 기록 +3. **progress.json** - [상태 파일 정의 > progress.json](#3-progressjson-템플릿) 템플릿으로 생성, totalTasks 업데이트 + +### Step 5: CLAUDE.md 업데이트 + +[CLAUDE.md 템플릿](#claudemd-템플릿) 섹션을 참조하여 CLAUDE.md 파일 업데이트 + +### Step 6: 완료 안내 + +[완료 안내 템플릿](#완료-안내-템플릿) 형식으로 안내 메시지 출력 + +--- + +## ✅ 완료 안내 템플릿 + +``` +✅ 학습 환경 설정 완료! + +📁 생성된 파일: +- .claude/agents/ (task-manager, guide, analyzer, checker) +- .claude/commands/ (start, end, setup, hint, check, done, commit) +- .claude/state/ (learning.md, tasks.md, progress.json, logs/) + +⚠️ 중요: /agents 메뉴에서 에이전트를 보려면 + Claude Code를 재시작하세요 (exit 후 claude 다시 실행) + +📋 설정된 내용: +- 학습 목표: [요약] +- 태스크: [N]개 + +🚀 다음 단계: +1. Claude Code 재시작 (exit → claude) +2. /agents 로 에이전트 확인 (4개 보여야 함) +3. 첫 번째 태스크 시작! + +💡 유용한 명령어: +- /start - 세션 시작 (매일 학습 시작 시) +- /end - 세션 종료 (학습 마무리 시) +- /hint - 힌트 요청 +- /check - 과제 검증 +- /done - 태스크 완료 +- /commit - 커밋 메시지 생성 +``` + +--- + +## 🔧 트러블슈팅 + +### 에이전트가 /agents에 안 보임 + +1. Claude Code 재시작했는지 확인 +2. `.claude/agents/` 폴더에 파일이 있는지 확인 +3. 각 파일이 `---`로 시작하는 YAML frontmatter가 있는지 확인 + +### 에이전트가 정답을 바로 알려줌 + +guide.md의 시스템 프롬프트에서 "⛔ 절대 금지" 섹션을 더 강조 + +### 로그가 생성 안 됨 + +task-manager.md의 "태스크 완료 시 (필수)" 섹션 확인 +로그 작성이 첫 번째 단계임을 강조 + +### 커밋 형식이 안 맞음 + +CLAUDE.md와 task-manager.md의 커밋 규칙 확인 +예시 추가 + +--- + +## 📌 Quick Reference + +### 에이전트 역할 + +| 에이전트 | 역할 | 코드 작성 | +| ------------ | --------------------------- | --------- | +| task-manager | 관리, 로깅, 세션 관리, 커밋 | ❌ | +| guide | 힌트, 개념 설명 | ❌ | +| analyzer | 코드 분석 | ❌ | +| checker | 검증, 피드백 | ❌ | + +### 힌트 레벨 + +| Level | 내용 | 예시 | +| ----- | ----------- | ---------------------------------------- | +| 1 | 방향성 | "useState를 활용해보세요" | +| 2 | 구체적 방향 | "useState로 입력값을 관리하세요" | +| 3 | 코드 스니펫 | "const [value, setValue] = useState('')" | +| 4 | 거의 정답 | 전체 코드 제공 (최후의 수단) | + +### 커밋 형식 + +``` +Type: 내용 (한글) + +* 세부 내용 +``` + +Type: Feat, Fix, Refactor, Style, Docs, Test, Chore diff --git a/docs/3-10-2025 Frontend Folder Structure Model.md b/docs/3-10-2025 Frontend Folder Structure Model.md new file mode 100644 index 000000000..0a178ce0c --- /dev/null +++ b/docs/3-10-2025 Frontend Folder Structure Model.md @@ -0,0 +1,521 @@ +# 3-10. 2025 프론트엔드 폴더 구조 모델 + +[폴더구조의 변화로 이해하는 프론트엔드 멘탈모델 변천사](https://velog.io/@teo/folder-structure) + +> 위 글은 제가 쓴 글이에요. 위 글와 아래 내용은 서로 연관은 있지만 다른 내용입니다. 한 마디로 둘 다 읽어보라는 말이에요. 😊 + +# Part 1: 현대적인 폴더 구조의 기초 + +여러분이 프론트엔드 개발을 하다 보면 "파일을 어디에 둬야 하지?"라는 질문을 자주 하게 됩니다. 현대 프론트엔드 개발에서 폴더 구조는 단순히 파일을 정리하는 것을 넘어서, 프로젝트의 아키텍처와 정신 모델을 물리적으로 구현하는 핵심적인 설계 활동이 되었습니다. 잘 설계된 구조는 코드의 복잡성을 관리하고, 팀원들과 협업을 원활하게 하며, 개발자의 인지 부하를 줄여 생산성을 극대화하는 전략적 자산이 됩니다. 이건 단순히 보기 좋게 만드는 문제가 아니라, 장기적으로 프로젝트의 성공과 실패를 가를 수 있는 중요한 엔지니어링 결정입니다. + +### 1.1. 핵심 원칙: 파일 구성을 넘어서 + +성공적인 폴더 구조를 만들기 위해서는 몇 가지 핵심적인 소프트웨어 설계 원칙을 이해해야 합니다. 이 원칙들은 코드를 논리적으로 그룹화하고 모듈 간의 의존성을 관리하여, 변화에 유연하고 확장하기 쉬운 시스템을 만드는 데 도움을 줍니다. + +### 높은 응집도와 낮은 결합도 (High Cohesion & Low Coupling) + +> 소프트웨어 설계를 배울 때 가장 먼저 알아야 할 두 원칙이 바로 높은 응집도와 낮은 결합도입니다. + +- **높은 응집도 (High Cohesion) :** 인증(Authentication) 기능을 구현한다고 생각해보세요. 전통적인 '타입 기반' 구조에서는 인증 관련 컴포넌트는 `/components` 폴더에, 관련 훅(hook)은 `/hooks` 폴더에, API 호출 로직은 `/services` 폴더에 흩어져 있게 됩니다. 이렇게 되면 낮은 응집도를 갖게 되는 거죠. 반면, '기능 기반' 구조를 배우면 `/features/auth`라는 폴더를 만들고 그 안에 인증 기능에 필요한 컴포넌트, 훅, 서비스 로직을 모두 함께 둡니다. 이렇게 하면 특정 기능을 수정하거나 이해해야 할 때 한 곳만 살펴보면 되므로 코드의 가독성과 유지보수성이 크게 향상됩니다. + +- **낮은 결합도 (Low Coupling):** 시스템의 한 부분이 변경될 때 다른 부분에 미치는 영향을 최소화하는 원칙입니다. 이를 달성하려면 모듈이 서로의 내부 구현에 깊이 의존하지 않고, 잘 정의된 인터페이스를 통해서만 상호작용하도록 해야 합니다. 예를 들어 각 기능 모듈이 명확한 '공개 API(Public API)'를 가지고, 다른 모듈은 오직 이 API를 통해서만 해당 기능에 접근하도록 하는 것이 좋은 방법입니다. 한 모듈의 내부 파일 구조가 변경되더라도 공개 API가 동일하게 유지된다면, 해당 모듈을 사용하는 다른 코드들은 전혀 영향을 받지 않습니다. 이것이 코드의 유연성과 재사용성을 높이는 핵심입니다. + +### DRY 원칙(코드 중복 방지)를 위한 성급한 추상화는 괴물 컴포넌트를 만든다. + +DRY 원칙은 코드 중복을 피하라는 유용한 지침이지만, 이를 맹목적으로 적용하면 오히려 문제가 될 수 있다는 것을 알아야 합니다. 너무 이르거나 잘못된 추상화는 약간의 코드 중복보다 더 큰 문제를 일으킬 수 있습니다. 예를 들어, 단지 코드 몇 줄이 비슷하다는 이유로 성급하게 공통 컴포넌트를 만들면, 각기 다른 요구사항을 수용하기 위해 수많은 속성(props)을 받는 "괴물 컴포넌트"가 만들어질 수 있습니다. + +**2025년의 프론트엔드 개발을 배우는 여러분은 DRY 원칙을 보다 미묘하게 해석하는 법을 익혀야 합니다.** 진정으로 재사용 가능하고 안정적인 로직(예: 디자인 시스템의 버튼, 날짜 포맷 유틸리티)은 공유 계층(shared layer)에 두어 중복을 피하는 것이 바람직합니다. 하지만 서로 다른 비즈니스 로직에 속해 있어 미래에 각기 다른 방향으로 변경될 가능성이 있는 코드라면, 약간의 중복을 감수하고 각 기능 폴더 내에 독립적으로 유지하는 것이 더 나은 선택일 수 있습니다. 잘못된 추상화와 싸우기보다는 약간의 중복을 허용하는 것이 장기적으로 유지보수 비용을 줄일 수 있다는 점을 기억하세요. + +### 개발자 경험(DX)과 온보딩 + +잘 설계된 폴더 구조는 개발자 경험(DX)을 크게 향상시킨다는 것을 배우게 될 것입니다. 명확하고 일관된 구조는 프로젝트의 전체적인 그림을 쉽게 파악하게 해주며, 새로운 팀원이 합류했을 때 온보딩 과정을 단축시키는 효과가 있습니다. 개발자가 특정 기능을 찾기 위해 여러 디렉토리를 헤매지 않아도 되고, 파일의 위치만으로 그 역할을 유추할 수 있게 되면, 불필요한 인지 부하가 줄어들어 더 중요한 비즈니스 로직 구현에 집중할 수 있게 됩니다. + +### 1.2. 확장성 스펙트럼: 프로토타입에서 엔터프라이즈까지 + +프로젝트의 규모와 복잡성이 증가함에 따라 폴더 구조에 대한 요구사항도 진화한다는 것을 이해하는 것이 중요합니다. 모든 프로젝트에 맞는 단 하나의 "최고의" 구조는 존재하지 않으며, 프로젝트의 현재 단계와 미래의 성장 가능성을 고려한 상황에 맞는 선택이 필요합니다. + +### 레벨 1: 역할 기반의 플랫 구조 (소규모 프로젝트 및 프로토타입) + +여러분이 아주 작은 프로젝트나 아이디어를 빠르게 검증하기 위한 프로토타입을 만들 때는 파일을 타입별로 그룹화하는 단순하고 평평한(flat) 구조가 효과적입니다. create-react-app이나 Vite 같은 도구들이 기본적으로 제공하는 구조처럼 `/components`, `/hooks`, `/utils` 등의 폴더를 사용하는 방식을 배우게 될 것입니다. 이 단계에서는 속도와 단순함이 가장 중요한 가치입니다. + +**한계**: 이 구조는 프로젝트가 조금만 커져도 금방 관리하기 어려워진다는 것을 경험하게 될 것입니다. 각 폴더에 파일이 수십 개씩 쌓이면 관련 파일을 찾기 힘들어지고, 하나의 비즈니스 로직이 여러 디렉토리에 흩어져 있어 전체적인 흐름을 파악하기 어렵게 됩니다. 이는 낮은 응집도로 인한 전형적인 문제입니다. + +### 레벨 2: 기능 그룹화 구조 (중대규모 프로젝트) + +프로젝트를 확장하기 위해 배워야 할 가장 중요하고 첫 번째 단계는 기능(feature) 또는 도메인(domain)을 기준으로 코드를 그룹화하는 것입니다. `/features/auth`, `/features/dashboard`와 같은 폴더를 만들고, 각 폴더 안에 해당 기능에만 사용되는 컴포넌트, 훅, 서비스 등을 배치하는 방식을 익히게 됩니다. + +**장점**: 이 접근법을 배우면 응집도를 극적으로 향상시킬 수 있습니다. 특정 기능과 관련된 모든 코드가 한곳에 모여 있기 때문에, 기능을 이해하거나 리팩토링하거나 심지어 제거하는 작업이 매우 용이해집니다. 이는 프로젝트의 유지보수성을 크게 높이는 효과를 가져옵니다. + +### 레벨 3: 엄격하게 강제되는 아키텍처 (대규모 및 엔터프라이즈 프로젝트) + +여러 팀이 참여하는 매우 크고 장기적인 프로젝트에서는 단순히 기능별로 그룹화하는 것만으로는 부족하다는 것을 배우게 됩니다. 시간이 지남에 따라 아키텍처가 무너지는 '아키텍처 부패(architectural decay)' 현상을 막기 위한 강력한 규칙이 필요합니다. 이 단계에서는 Feature-Sliced Design(FSD)과 같은 엄격한 아키텍처 방법론을 학습하게 됩니다. FSD는 계층(layer) 간의 의존성 흐름을 엄격하게 통제하여 시스템의 안정성을 유지하고 예측 가능성을 높입니다. + +**요구사항**: 이 수준의 아키텍처를 유지하기 위해서는 팀의 높은 이해도와 규율뿐만 아니라, 아키텍처 규칙을 자동으로 강제하는 도구(예: 린터, 코드 생성기)의 도입이 필수적이라는 것을 배우게 됩니다. + +이러한 스펙트럼을 이해하면 폴더 구조를 정적인 것이 아닌, 프로젝트의 생명주기에 따라 진화하는 동적인 것으로 바라보게 됩니다. 초기에는 단순한 구조로 시작하더라도, 프로젝트가 성장함에 따라 발생하는 문제점(pain point)을 해결하기 위해 점진적으로 더 구조화된 방식으로 전환하는 전략을 배우는 것이 중요합니다. + +이 과정에서 폴더 구조는 단순한 파일 정리 체계를 넘어, 아키텍처의 대리인(proxy) 역할을 수행한다는 것을 이해하게 됩니다. 개발자가 코드를 배치하는 방식은 곧 시스템의 의존성과 복잡성을 관리하는 방식에 대한 선언과 같습니다. 예를 들어, '타입 기반' 구조에서 '기능 기반' 구조로 전환하는 것은 단순히 파일을 옮기는 것이 아니라, 응집도를 높이고 결합도를 낮추려는 명확한 아키텍처적 결정입니다. 더 나아가 FSD를 도입하는 것은 아키텍처 부패를 막기 위한 더욱 진보된 아키텍처적 결정이라는 것을 배우게 됩니다. + +또한, 실용적인 관점에서 배워야 할 중요한 점은 대부분의 성공적인 아키텍처는 순수한 형태가 아닌 하이브리드 모델을 채택한다는 것입니다. 기능 기반 구조에서도 전역적으로 사용되는 UI 컴포넌트나 유틸리티 함수를 위한 `/shared`, `/lib`, `/core`와 같은 최상위 공유 디렉토리는 반드시 필요합니다. 진정으로 재사용 가능한 자산을 특정 기능 폴더 내에 두는 것은 잘못된 의존성을 만들어내기 때문입니다. 따라서 가장 실용적이고 확장 가능한 아키텍처는 비즈니스 로직을 위한 기능 기반 그룹화와, 애플리케이션에 특화되지 않은 범용 유틸리티 및 UI 요소를 위한 잘 정의된 공유 계층을 결합하는 형태가 된다는 것을 배우게 됩니다. 여기서 핵심 과제는 '어떤 코드가 특정 기능에 속하고, 어떤 코드가 공유되어야 하는가'에 대한 명확한 경계를 설정하는 방법을 익히는 것입니다. + +## Part 2: 주요 아키텍처 방법론: 비교 분석 + +프론트엔드 아키텍처 분야를 공부하다 보면 복잡성을 관리하고 코드의 품질을 높이기 위한 여러 가지 성숙한 방법론들을 만나게 됩니다. 이들은 각기 다른 철학을 바탕으로 하지만, 모두 확장 가능하고 유지보수하기 쉬운 애플리케이션을 구축한다는 공통된 목표를 가집니다. 여기서는 가장 영향력 있는 세 가지 방법론인 아토믹 디자인, 도메인 주도 설계, 그리고 피처 슬라이스 디자인을 자세히 배워보겠습니다. + +### 2.1. 컴포넌트 중심 접근법: 아토믹 디자인 (Atomic Design) + +**철학**: 브래드 프로스트(Brad Frost)에 의해 소개된 아토믹 디자인을 배우면, UI를 다섯 단계의 뚜렷한 계층으로 분해하여 디자인 시스템을 구축하는 방법을 익히게 됩니다. 이는 UI를 하나의 응집력 있는 전체이면서 동시에 여러 부분의 집합으로 생각하는 정신 모델을 제공합니다. 화학의 원자, 분자, 유기체 개념을 차용하여 컴포넌트의 계층 구조를 명확하게 정의하는 방법을 배우게 됩니다. + +**5단계 계층** + +**원자 (Atoms)**: UI의 가장 기본적인 구성 요소로, 더 이상 분해할 수 없는 최소 기능 단위를 배우게 됩니다. 예를 들어 버튼, 인풋 필드, 레이블, 아이콘과 같은 HTML 요소나 색상 팔레트, 폰트와 같은 디자인 속성이 여기에 해당합니다. + +**분자 (Molecules)**: 여러 개의 원자가 결합하여 하나의 단위로 함께 기능하는 단순한 그룹을 만드는 방법을 배웁니다. 예를 들어, 레이블 원자, 인풋 원자, 버튼 원자가 결합하여 검색 폼 분자를 구성할 수 있습니다. 이 단계의 컴포넌트는 단일 책임 원칙을 따르는 재사용 가능한 단위가 됩니다. + +**유기체 (Organisms)**: 분자와 원자, 또는 다른 유기체가 결합하여 형성되는 더 복잡하고 독립적인 인터페이스의 한 부분을 만드는 방법을 익힙니다. 웹사이트의 헤더는 로고(원자), 주 메뉴(분자), 검색 폼(분자)이 결합된 유기체의 좋은 예입니다. + +**템플릿 (Templates)**: 실제 콘텐츠 없이 페이지의 전체적인 레이아웃과 구조를 정의하는 단계를 배웁니다. 여러 유기체와 분자를 배치하여 페이지의 뼈대를 만듭니다. 이는 동적인 콘텐츠가 채워질 것을 대비하여 설계의 일관성을 유지하는 데 중요한 역할을 합니다. + +**페이지 (Pages)**: 템플릿에 실제 콘텐츠(텍스트, 이미지 등)를 채워 넣어 사용자에게 보여지는 최종 UI의 구체적인 인스턴스를 만드는 방법을 배웁니다. 이 단계를 통해 디자인 시스템이 실제 데이터와 만났을 때 얼마나 효과적이고 견고한지 테스트할 수 있습니다. + +**폴더 구조 구현 방법을 배워봅시다**: 이 방법론은 `/components` 또는 `/ui` 디렉토리 내에서 직관적인 폴더 구조로 변환될 수 있습니다. + +``` +/src + └── /components + ├── /1_atoms + │ ├── Button.tsx + │ └── Input.tsx + ├── /2_molecules + │ └── SearchForm.tsx + ├── /3_organisms + │ └── PageHeader.tsx +/src + ├── /4_templates + │ └── HomepageTemplate.tsx + └── /5_pages + └── HomePage.tsx + +``` + +**밝혀진 장단점:** +아토믹 디자인은 디자인 시스템이나 컴포넌트 중심의 애플리케이션을 구축하고 유지보수하는 데 매우 효과적이라는 것을 배우게 됩니다. 재사용성과 일관성을 극대화하는 장점이 있습니다. + +하지만 소규모 프로젝트에서는 과도한 계층 구조로 인해 불필요한 복잡성을 초래할 수 있으며, 비즈니스 로직이나 데이터 흐름을 어떻게 구성할지에 대한 해답을 제공하지는 않는다는 한계도 알아두어야 합니다. + +이 때문에 아토믹 디자인은 독립적인 아키텍처라기보다는, 다른 아키텍처 내에서 UI 컴포넌트를 체계적으로 관리하기 위한 패턴으로 사용되는 경우가 많습니다(예: FSD의 shared/ui 세그먼트). + +### 2.2. 비즈니스 중심 접근법: 도메인 주도 설계 (Domain-Driven Design, DDD) 원칙 + +DDD는 소프트웨어의 구조를 비즈니스 도메인에 맞추는 것을 목표로 하는 개발 접근법이라는 것을 배우게 됩니다. 개발자와 도메인 전문가가 공유하는 '유비쿼터스 언어(Ubiquitous Language)'를 통해 소통하고, 이를 코드에 직접 반영하는 것을 강조합니다. 본래 백엔드에서 시작되었지만, 복잡한 비즈니스 로직을 다루는 현대 프론트엔드 애플리케이션에서도 그 원칙이 활발히 적용되고 있다는 것을 알게 될 것입니다. DDD의 핵심은 기술이 아닌 '제품'과 '비즈니스' 자체에 집중하는 것입니다. + +**프론트엔드를 위한 DD 핵심 개념을 톺아보기** + +**바운디드 컨텍스트 (Bounded Context)**: 특정 도메인 모델이 적용되는 명확한 경계를 의미한다는 것을 배웁니다. 프론트엔드에서는 하나의 주요 기능 영역이나 마이크로 프론트엔드가 하나의 바운디드 컨텍스트에 해당할 수 있습니다. + +**도메인 (Domain) 및 하위 도메인 (Subdomain)**: 도메인은 애플리케이션이 다루는 비즈니스 영역(예: 전자상거래, 헬스케어)을 의미한다는 것을 배웁니다. 전략적 설계를 통해 도메인을 더 작은 하위 도메인으로 나누고, 각각을 핵심(Core, 경쟁 우위), 지원(Supporting), 일반(Generic, 표준 솔루션)으로 분류하여 자원을 집중할 곳을 결정하는 방법을 익히게 됩니다. + +**폴더 구조 구현 방법**: DDD에서 영감을 받은 폴더 구조는 기술적 역할이 아닌 비즈니스 하위 도메인을 기준으로 코드를 구성한다는 것을 배웁니다. 이는 자연스럽게 높은 응집도를 이끌어냅니다. + +``` +/src + └── /domains // 또는 /features, /modules + ├── /ordering + │ ├── /entities // Order, OrderItem 모델 + │ ├── /useCases // PlaceOrder, CancelOrder 로직 + │ ├── /components // OrderForm, OrderHistory 컴포넌트 + │ └── /services // 주문 관련 API 호출 + ├── /catalog + │ ├── /entities // Product 모델 + │ ├── /useCases // SearchProducts 로직 + │ └── /components // ProductGrid, ProductDetail 컴포넌트 + └── /shared-kernel // CustomerID, Money 등 공유 개념 + +``` + +**밝혀진 장단점:** +DDD는 비즈니스 로직이 복잡하고 명확하게 정의된 대규모 애플리케이션에 매우 적합하다는 것을 배우게 됩니다. 비즈니스 도메인을 이해하는 개발자에게는 코드가 매우 직관적으로 다가옵니다. 하지만 깊은 도메인 지식을 요구하며, 팀이 '프론트엔드'를 제품 도메인의 일부가 아닌 별개의 기술적 바운디드 컨텍스트로 오해할 경우 잘못 적용될 위험이 있다는 점도 알아두어야 합니다. + +### 2.3. 기능 중심 접근법: 피처 슬라이스 디자인 (Feature-Sliced Design, FSD) + +**철학**: FSD는 프론트엔드 애플리케이션을 스캐폴딩하기 위한 엄격한 아키텍처 방법론으로, 프로젝트가 확장됨에 따라 코드베이스를 이해하기 쉽고 안정적으로 유지하기 위해 설계되었다는 것을 배우게 됩니다. 코드를 계층(Layers), 슬라이스(Slices), 세그먼트(Segments)라는 세 가지 축으로 구성하고, 이들 간의 의존성에 대한 엄격한 규칙을 적용합니다. + +**핵심 계념 톺아보기** + +**계층 (Layers)**: FSD는 총 7개의 계층으로 구성되며, 상위 계층은 하위 계층을 가져올 수 있지만 그 반대는 불가능한 계층적 구조를 가진다는 것을 배웁니다. 이 규칙은 순환 의존성을 방지하고 변경의 영향을 국소화합니다. + +- **app**: 애플리케이션의 진입점으로, 초기화, 라우팅, 전역 스타일 및 스토어 설정 등을 담당합니다. +- **pages**: 완전한 애플리케이션 페이지로, 위젯과 기능들로 구성됩니다. +- **widgets**: 독립적이고 큰 UI 블록입니다 (예: 헤더, 사이드바, 게시물 댓글 목록). +- **features**: 사용자 상호작용과 관련된 비즈니스 가치를 제공하는 로직입니다 (예: 인증, 댓글 달기, 장바구니에 추가). +- **entities**: 애플리케이션이 다루는 비즈니스 개체, 즉 '명사'입니다 (예: 사용자, 제품, 댓글). +- **shared**: 비즈니스 로직과 무관한 재사용 가능한 코드입니다 (예: UI 키트, API 클라이언트, 유틸리티 함수). + +**슬라이스 (Slices)**: 계층 내에서 비즈니스 도메인을 기준으로 코드를 그룹화하는 폴더라는 것을 배웁니다 (예: features/authentication, entities/user). 같은 계층 내의 슬라이스들은 서로를 직접 가져올 수 없어 모듈성을 강제합니다. + +**세그먼트 (Segments)**: 각 슬라이스는 기술적 목적에 따라 코드를 분리하는 표준화된 하위 폴더로 나뉜다는 것을 익히게 됩니다 (예: ui, model, api, lib, config). + +**공개 API (Public API)**: 각 슬라이스는 반드시 index.ts 파일을 통해 자신의 공개 API를 외부에 노출해야 한다는 중요한 규칙을 배웁니다. 애플리케이션의 다른 부분은 오직 이 파일을 통해서만 해당 슬라이스의 기능에 접근할 수 있으며, 이를 통해 슬라이스의 내부 구현을 완벽하게 캡슐화합니다. + +**밝혀진 장단점:** + +**장점**: 확장성이 매우 뛰어나고 구조가 통일되어 있어 대규모 팀과 장기 프로젝트에 이상적이라는 것을 배우게 됩니다. 일단 학습하고 나면 새로운 팀원의 온보딩이 용이하며, 코드 재사용이 통제되고 리팩토링이 안전합니다. + +**단점**: 학습 곡선이 가파르고 초기 설정이 복잡하다는 점을 알아두어야 합니다. 소규모 프로젝트에는 과도한 설계(over-engineering)가 될 수 있으며, 기존 프로젝트를 FSD로 마이그레이션하는 것은 매우 어려운 작업입니다. 때로는 규칙이 너무 엄격하게 느껴질 수도 있습니다. + +### 2.4. 종합 및 하이브리드 모델 + +이 세 가지 방법론은 상호 배타적이지 않으며, 오히려 서로를 보완하며 함께 사용될 때 강력한 시너지를 낼 수 있다는 것을 배우게 됩니다. + +**사례 1: FSD 내에서의 아토믹 디자인**: shared/ui 세그먼트를 아토믹 디자인 원칙에 따라 구성하는 것은 매우 일반적이고 효과적인 패턴이라는 것을 익히게 됩니다. atoms, molecules, organisms가 애플리케이션의 다른 부분에서 소비하는 UI 키트가 됩니다. + +**사례 2: FSD 슬라이스 정의를 위한 DDD**: DDD의 비즈니스 하위 도메인과 엔티티를 식별하는 과정은 FSD의 entities와 features 계층에 대한 슬라이스를 정의하는 훌륭한 출발점이 될 수 있다는 것을 배웁니다. DDD가 '무엇'을 정의하고(예: User, Product), FSD가 '어디에' 배치할지를 결정하는 것입니다. + +**사례 3: "FSD-Lite"로서의 기능 기반 구조**: 단순한 기능 기반 구조는 FSD의 7개 계층을 모두 사용하지 않고 features와 shared 개념만 차용한, 덜 엄격한 버전의 FSD로 볼 수 있다는 것을 이해하게 됩니다. 이는 많은 프로젝트에 실용적인 출발점을 제공합니다. + +이러한 방법론들을 비교 분석해보면서 여러분은 중요한 통찰을 얻게 됩니다. 서로 다른 출발점에도 불구하고 결국에는 동일한 핵심 문제, 즉 대규모 UI 개발에서의 복잡성 관리, 경계 설정, 재사용성 향상을 해결하려 한다는 것을 알 수 있습니다. 아토믹 디자인은 UI 컴포넌트 수준에서, DDD는 비즈니스 로직 수준에서, FSD는 애플리케이션 전체 구조 수준에서 복잡성을 다룹니다. 따라서 가장 성숙한 아키텍처는 이 세 가지 방법론의 개념을 모두 차용할 가능성이 높다는 것을 배우게 됩니다. + +또한, 이 모든 방법론에서 공통적으로 발견되는 중요한 패턴 중 하나는 모듈의 '공개 API' 개념이라는 것을 알게 됩니다. index.ts와 같은 배럴 파일을 사용하여 모듈의 진입점을 단일화하고 내부 구현을 숨기는 방식은, 채택한 아키텍처 방법론과 관계없이 확장성과 유지보수성을 확보하기 위한 보편적인 핵심 패턴입니다. 이는 낮은 결합도와 높은 캡슐화를 달성하는 가장 기본적인 메커니즘이라는 것을 기억하세요. + +## Part 3: 모던 프레임워크 모범사례 살펴보기 + +추상적인 아키텍처 방법론을 실제 프로젝트에 적용하기 위해서는 사용하는 프레임워크의 특성을 고려한 구체적인 구현 계획이 필요하다는 것을 배우게 됩니다. 이 파트에서는 React, Next.js, Vue, Angular 등 주요 프레임워크 환경에서 각 아키텍처 원칙을 어떻게 구체적인 폴더 구조로 구현할 수 있는지 알아보겠습니다. + +### 3.1. React & TypeScript 생태계 + +2025년 React 프로젝트의 표준으로 기능 기반(feature-based) 구조가 강력하게 권장된다는 것을 알아두세요. 이는 프로젝트가 성장함에 따라 유지보수성과 확장성을 확보하는 가장 실용적인 방법입니다. 아래는 많은 프로젝트에 적용할 수 있는 포괄적인 구조 예시입니다. + +``` +/src + ├── /app # 전역 설정: 라우팅, 프로바이더, 진입점 + ├── /components # 또는 /shared/ui: 진정으로 재사용 가능한 전역 컴포넌트 (Button, Modal) + ├── /features # 기능별 모듈 + │ └── /authentication + │ ├── /components # LoginForm, SignupForm 등 이 기능에서만 사용하는 컴포넌트 + │ ├── /hooks # useAuth, useLogin 등 이 기능에 특화된 훅 + │ ├── /services # authApi.ts 등 API 관련 로직 + │ ├── index.ts # auth 기능의 공개 API (외부 노출) + │ └── AuthPage.tsx # 이 기능을 대표하는 페이지 컴포넌트 + ├── /hooks # 전역적으로 공유되는 훅 (useLocalStorage, useTheme) + ├── /lib # 또는 /utils: 전역 유틸리티 함수 (날짜 포맷팅 등) + ├── /services # 전역 API 클라이언트, 외부 서비스 초기화 + ├── /store # 전역 상태 관리 (Redux, Zustand) + └── /types # 전역 TypeScript 타입 정의 + +``` + +이 하이브리드 구조는 FSD처럼 엄격하지는 않으면서도 관심사의 분리를 명확하게 하여 많은 프로젝트에 이상적인 균형점을 제공한다는 것을 이해하게 됩니다. 각 디렉토리의 역할은 명확합니다. `/features`는 비즈니스 로직을 캡슐화하고, `/components`와 `/hooks` 같은 최상위 폴더는 여러 기능에 걸쳐 재사용되는 범용적인 코드를 보관합니다. + +**전역 TypeScript 타입 정의** + +- 전역적으로 사용되는 타입은 `/types` 폴더에 중앙 집중화하되, 특정 컴포넌트나 기능에만 사용되는 타입은 해당 컴포넌트/기능 폴더 내에 함께 배치(co-location)하는 것이 좋다는 것을 배웁니다. +- 전역 네임스페이스를 오염시킬 수 있는 `.d.ts` 파일의 남용을 피하고, 명시적인 import/export를 통해 타입을 관리해야 한다는 것을 알게 됩니다. +- 각 기능 모듈의 공개 API인 index.ts 파일을 통해 컴포넌트, 훅뿐만 아니라 관련 타입도 함께 내보내어 사용성을 높이는 방법을 익히게 됩니다. + +### 3.2. Next.js: App Router 탐색 + +Next.js 13 이상에서 도입된 App Router는 파일 시스템 기반 라우팅을 통해 프로젝트 구조를 근본적으로 정의한다는 것을 배우게 됩니다. 폴더는 URL 경로 세그먼트를 정의하고, page.tsx, layout.tsx, loading.tsx, error.tsx와 같은 특수 파일들은 해당 경로의 UI를 구성합니다. + +**특징1. Co-location** + +App Router는 컴포넌트, 스타일, 테스트 파일 등을 사용하는 라우트와 같은 폴더에 배치하는 co-location을 장려한다는 것을 알게 됩니다. 이는 자연스럽게 기능 기반 아키텍처와 잘 맞아떨어집니다. + +**특징2. 라우트 그룹을 통한 구성** + +URL 경로에 영향을 주지 않으면서 라우트를 논리적으로 그룹화하거나, 특정 그룹에만 공유 레이아웃을 적용하기 위해 괄호로 폴더 이름을 감싸는 '라우트 그룹(Route Groups)'은 매우 중요한 기술이라는 것을 배웁니다. 예를 들어 (marketing)이나 (shop) 같은 폴더를 만들어 관련 페이지들을 묶을 수 있습니다. 이는 Next.js가 제공하는 기능 도메인을 만드는 네이티브 메커니즘입니다. + +**Next.js 에서 제안하는 모범 사례** + +이러한 기능들을 활용한 구조는 코드 구성을 명확하게 하기 위해 src 디렉토리 내에 배치하는 것이 일반적이라는 것을 배웁니다. + +``` +/src + ├── /app + │ ├── /(marketing) # 마케팅 페이지 라우트 그룹 + │ │ ├── /about + │ │ │ └── page.tsx + │ │ └── layout.tsx # 마케팅 페이지들을 위한 레이아웃 + │ ├── /(shop) # 메인 애플리케이션 라우트 그룹 + │ │ ├── /products + │ │ │ └── page.tsx + │ │ └── layout.tsx # 메인 쇼핑몰을 위한 레이아웃 + │ ├── /api # API 라우트 + │ └── layout.tsx # 최상위 루트 레이아웃 + ├── /components # 전역 공유 UI 컴포넌트 (Button 등) + ├── /lib # 공유 유틸리티, DB 클라이언트 등 + ├── /features # 페이지와 직접 관련 없는 비즈니스 로직/상태 + │ └── /cart + │ ├── use-cart-store.ts + │ └── index.ts + └── /styles # 전역 스타일 + +``` + +**모듈 기반 아키텍처** + +대규모 Next.js 프로젝트에서는 '모듈 기반' 아키텍처가 효과적이라는 것을 배우게 됩니다. 이는 공유 로직을 위한 강력한 core 모듈과, 인증(authentication)과 같은 특정 기능을 캡슐화하는 여러 기능 모듈로 구성된 기능 기반 구조의 심화 버전입니다. 이 패턴은 Next.js 프로젝트의 확장성을 크게 향상시킨다는 것을 알게 됩니다. + +### 3.3. Vue & Nuxt.js: 모듈성과 규칙 기반 + +React와 마찬가지로 대규모 Vue 애플리케이션에서는 기능 기반 또는 모듈식 구조가 권장된다는 것을 배웁니다. Vue 공식 스타일 가이드에서 권장하는 컴포넌트 이름은 파스칼 케이스(PascalCase)를 사용하고, 앱 전반에 걸쳐 사용되는 기본 컴포넌트에는 Base 접두사를 붙이는 등의 네이밍 컨벤션을 따르는 것이 좋다는 것을 알게 됩니다. + +**Nuxt.js 디렉토리 구조** + +Nuxt는 '규칙보다 설정(convention-over-configuration)' 철학을 바탕으로, 특정 폴더에 파일을 위치시키면 자동으로 필요한 기능이 활성화되는 강력한 디렉토리 구조를 가지고 있다는 것을 배웁니다. + +- **components/**: Vue 컴포넌트를 저장하면 자동으로 임포트됩니다. 폴더를 중첩하여 ``과 같이 네임스페이스가 있는 컴포넌트를 만들 수 있다는 것을 배웁니다. +- **composables/**: Vue Composition API 함수(use...로 시작하는)를 저장하면 자동으로 임포트된다는 것을 알게 됩니다. +- **layouts/**: 여러 페이지에서 재사용 가능한 페이지 레이아웃을 정의하는 방법을 배웁니다. +- **pages/**: 파일 시스템 기반으로 애플리케이션의 라우트를 자동으로 생성한다는 것을 이해하게 됩니다. +- **assets/ vs public/**: 이 둘의 차이를 이해하는 것은 매우 중요합니다. assets 폴더에는 빌드 도구(Vite/Webpack)에 의해 처리될 파일(예: SCSS, 최적화가 필요한 이미지)을 넣고, public 폴더에는 빌드 과정 없이 서버 루트에 그대로 제공될 정적 파일(예: robots.txt, favicon.ico)을 넣는다는 것을 배웁니다. + +**Nuxt가 제안하는 폴더구조 모범사례** + +Vue 3에 권장되는 모듈식 접근법을 Nuxt의 규칙 기반 시스템에 적용한 구조를 배우게 됩니다. + +``` +/ + ├── /modules # 기능별 모듈 + │ └── /products + │ ├── /components # ProductCard, ProductFilter + │ ├── /composables# useProducts (자동 임포트) + │ └──... + ├── /components # 전역 컴포넌트 (BaseButton, BaseModal 등) + ├── /composables # 전역 컴포저블 + ├── /assets # 스타일시트, 폰트 등 빌드 처리 대상 + ├── /public # 정적 에셋 + ├── /layouts # Default, Auth 레이아웃 + ├── /pages # 라우팅을 위한 페이지 + └── nuxt.config.ts + +``` + +### 3.4. Angular: 엔터프라이즈급 모듈성 + +Angular 아키텍처의 중심에는 캡슐화와 의존성 주입을 위한 공식적인 구조를 제공하는 NgModule 시스템이 있다는 것을 배웁니다. 표준적인 모범 사례는 '기능 모듈(feature module)' 아키텍처입니다. + +**3가지 핵심 모듈 타입 (core / shared / features)** + +**CoreModule**: 애플리케이션 전체에서 단 한 번만 인스턴스화되어야 하는 싱글톤 서비스(singleton service)와 단일 사용 컴포넌트(예: AppComponent, Header)를 포함한다는 것을 배웁니다. 이 모듈은 루트 모듈인 AppModule에서 단 한 번만 임포트되어야 합니다. 이는 서비스의 다중 인스턴스 생성을 방지합니다. + +**SharedModule**: 여러 기능 모듈에서 공통으로 사용되는 재사용 가능한 컴포넌트, 디렉티브, 파이프를 포함한다는 것을 알게 됩니다. 이 모듈은 필요한 기능 모듈 어디에서든 임포트될 수 있습니다. 중요한 점은, SharedModule은 서비스를 제공(provide)해서는 안 된다는 것입니다. + +**FeatureModule**: 특정 비즈니스 기능(예: UsersModule, OrdersModule)을 캡슐화한다는 것을 배웁니다. 기능 모듈은 공통 UI 요소를 위해 SharedModule을 임포트하고, 전역 서비스를 사용하기 위해 CoreModule에 의존합니다 (직접 임포트하는 것이 아니라, AppModule을 통해 제공받음). + +**Angular가 제안하는 모범 사례** + +폴더 구조는 이러한 모듈식 접근법을 직접적으로 반영하며, Angular CLI를 통해 쉽게 생성하고 관리할 수 있다는 것을 배웁니다. + +``` +/src + ├── /app + │ ├── /core + │ │ ├── /guards # AuthGuard + │ │ ├── /services # AuthService, LoggerService + │ │ └── core.module.ts + │ ├── /features + │ │ └── /users + │ │ ├── /components # UserListComponent, UserEditComponent + │ │ ├── users.module.ts + │ │ └── users-routing.module.ts + │ ├── /shared + │ │ ├── /components # ModalComponent, LoaderComponent + │ │ ├── /pipes # FormatDatePipe + │ │ └── shared.module.ts + │ ├── app.component.ts + │ ├── app.module.ts + │ └── app-routing.module.ts + └── /assets + +``` + +결론적으로 여러분이 배우게 될 중요한 통찰은, 각 프레임워크는 고유의 특성과 도구를 가지고 있지만, **현대 프론트엔드 개발의 큰 흐름은 모두 기능 기반의 모듈식 아키텍처를 지향하고 있다는 것입니다.** + +과거에는 개발자의 선택에 맡겨졌던 구조화 방식이 이제는 프레임워크 자체에 내장된 기능(Next.js의 라우트 그룹, Angular의 모듈 시스템, Nuxt의 규칙 기반 자동화)을 통해 적극적으로 권장되고 있습니다. 이는 업계 전체가 과거의 평평한 구조에서 겪었던 고통을 통해 학습하고 발전한 결과이며, 이러한 흐름에 따르는 것이 프레임워크의 장점을 최대한 활용하고 장기적으로 성공적인 프로젝트를 구축하는 방법이라는 것을 배우게 됩니다. + +## Part 4: 엔터프라이즈로의 확장: 모노레포와 도구 + +프로젝트의 규모가 커져 여러 애플리케이션과 라이브러리가 공존하고, 다수의 팀이 협업하는 엔터프라이즈 환경에 도달하면 코드 관리와 아키텍처 유지를 위한 새로운 차원의 전략이 필요하다는 것을 배우게 됩니다. 이 단계에서는 모노레포(Monorepo) 아키텍처와 아키텍처 규칙을 자동화하는 도구의 역할이 매우 중요해집니다. + +### 4.1. 모노레포 아키텍처 + +**모노레포를 사용하는 이유** + +모노레포는 여러 프로젝트의 소스 코드를 단일 저장소에서 관리하는 방식이라는 것을 배웁니다. 주요 장점으로는 UI 컴포넌트나 유틸리티 함수와 같은 코드의 손쉬운 공유, 공유 라이브러리 업데이트 시 모든 의존 프로젝트에 변경사항을 한 번에(atomically) 적용할 수 있는 점, 그리고 여러 프로젝트에 걸친 대규모 리팩토링의 용이성 등이 있습니다. 이는 공유 플랫폼을 기반으로 하거나 여러 개의 상호 연결된 애플리케이션을 개발하는 조직에 특히 이상적이라는 것을 알게 됩니다. + +**핵심 구조: apps와 packages** + +Turborepo나 Nx와 같은 현대적인 모노레포 관리 도구들은 표준적인 디렉토리 구조를 권장한다는 것을 익히게 됩니다. 이는 배포 가능한 애플리케이션과 공유 가능한 코드를 명확히 분리하는 것입니다. + +**apps/**: 실제 사용자에게 배포될 최종 애플리케이션들이 위치하는 디렉토리라는 것을 배웁니다. 예를 들어, Next.js로 만든 웹사이트, Vite 기반의 관리자 대시보드, Storybook으로 구현된 문서 사이트 등이 여기에 해당합니다. + +**packages/**: apps 디렉토리의 애플리케이션들이 공통으로 사용하는 공유 라이브러리들이 위치하는 디렉토리라는 것을 알게 됩니다. 이 패키지들은 독립적으로 배포되지 않고, 다른 애플리케이션에 의해 소비되는 것을 목적으로 합니다. + +**일반적인 공유 패키지를 이해해봅시다**: + +- **packages/ui**: 여러 애플리케이션에서 공통으로 사용되는 React, Vue 등의 UI 컴포넌트 라이브러리입니다. 이는 조직의 디자인 시스템의 핵심이 됩니다. +- **packages/tsconfig**: 여러 패키지에서 확장(extend)하여 사용할 수 있는 중앙 집중화된 TypeScript 설정을 제공하여 일관성을 유지합니다. +- **packages/eslint-config-custom**: 모든 앱과 패키지에 걸쳐 일관된 코딩 표준을 강제하기 위한 공유 ESLint 설정입니다. +- **packages/lib 또는 packages/utils**: 프레임워크에 종속되지 않는 순수한 유틸리티 함수들을 모아놓은 패키지입니다. + +**모노레포를 위한 도구** + +**ㅋ**모노레포를 효율적으로 관리하기 위해서는 Turborepo나 Nx와 같은 빌드 시스템이 필수적이라는 것을 알게 됩니다. 이 도구들은 변경된 부분만 빌드하고 테스트하는 '증분 빌드(incremental builds)', 빌드 결과를 저장하고 재사용하는 '캐싱(caching)', 그리고 패키지 간의 의존성 관계를 분석하는 '의존성 그래프 분석'과 같은 핵심 기능을 제공하여 대규모 저장소에서도 빠른 개발 속도를 유지할 수 있게 해줍니다. + +모노레포 아키텍처는 기능 기반 구조와 매우 자연스럽게 결합된다는 것을 배우게 됩니다. 모노레포의 주된 목적인 코드 공유를 촉진하는 과정에서, 개발팀은 자연스럽게 무엇이 공유 가능한 package이고 무엇이 특정 애플리케이션에 속한 코드(apps)인지를 결정하게 됩니다. 이 과정 자체가 기능과 공유 코드의 경계를 명확히 하도록 유도하며, 모노레포의 물리적 구조가 아키텍처적 분리를 위한 가이드 역할을 하게 된다는 것을 이해하게 됩니다. + +### 4.2. 폴더 구조를 위한 도구들 + +대규모 프로젝트에서 아키텍처 규칙은 단순히 문서나 구두 약속, 코드 리뷰만으로는 지켜지기 어렵다는 것을 배우게 됩니다. 시간이 지나고 팀원이 늘어남에 따라 아키텍처는 점차 무너지기 쉽습니다. 따라서 '올바른 방법'을 '쉬운 방법'으로 만들고, 규칙 위반을 원천적으로 방지하는 자동화된 도구의 도입이 필수적이라는 것을 알게 됩니다. + +### Plop.js를 이용한 자동화된 스캐폴딩 + +**목적을 이해해봅시다**: Plop.js는 템플릿을 기반으로 새로운 파일과 폴더 구조를 생성해주는 "마이크로 제너레이터"라는 것을 배웁니다. 이를 활용하면 새로운 컴포넌트나 기능을 추가할 때마다 정해진 폴더 구조와 필요한 파일들(컴포넌트, 스타일, 테스트, 스토리북 등)이 일관되게 생성되도록 자동화할 수 있습니다. 이는 개발자의 반복 작업을 줄여줄 뿐만 아니라, 아키텍처의 일관성을 유지하는 데 결정적인 역할을 합니다. + +**예시를 통해 배워봅시다**: 아래는 React 컴포넌트를 생성하는 plopfile.js와 관련 템플릿 예시입니다. 이 생성기는 컴포넌트 이름(ComponentName)을 입력받아 지정된 구조에 맞춰 컴포넌트, 스타일, 테스트, 스토리북 파일을 자동으로 생성합니다. + +plopfile.js 설정: + +```jsx +module.exports = function (plop) { + plop.setGenerator("component", { + description: "Create a new React component", + prompts: [ + { + type: "input", + name: "name", + message: "Component name please", + }, + ], + actions: [ + { + type: "addMany", + destination: "src/components/{{pascalCase name}}", + base: "plop-templates/component", + templateFiles: "plop-templates/component/**", + }, + ], + }) +} +``` + +템플릿 파일 (/plop-templates/component/): + +{{pascalCase name}}.tsx.hbs: + +``` +import React from 'react'; +import styles from './{{pascalCase name}}.module.css'; + +export const {{pascalCase name}} = () => { + return
{{pascalCase name}}
; +}; + +``` + +{{pascalCase name}}.test.tsx.hbs: + +``` +import { render } from '@testing-library/react'; +import { {{pascalCase name}} } from './{{pascalCase name}}'; + +it('renders correctly', () => { + render(<{{pascalCase name}} />); +}); + +``` + +{{pascalCase name}}.stories.tsx.hbs: + +``` +import type { Meta, StoryObj } from '@storybook/react'; +import { {{pascalCase name}} } from './{{pascalCase name}}'; + +const meta: Meta = { + title: 'Components/{{pascalCase name}}', + component: {{pascalCase name}}, +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +``` + +### ESLint를 이용한 경계 정의 + +**목적을 알아봅시다**: ESLint는 코드 스타일뿐만 아니라 아키텍처 규칙을 강제하는 강력한 도구로 활용될 수 있다는 것을 배웁니다. 잘못된 import 구문을 감지하여 아키텍처의 계층과 모듈 간의 경계를 보호하는 것이 핵심입니다. 예를 들어, features 계층의 한 모듈이 다른 모듈을 직접 임포트하는 것을 금지하거나, 모듈의 내부 파일에 직접 접근하는 것을 막을 수 있습니다. + +**기본 규칙: no-restricted-imports를 익혀봅시다**: ESLint에 내장된 이 규칙은 특정 모듈이나 경로 패턴의 임포트를 금지하는 간단하면서도 효과적인 방법을 제공한다는 것을 배웁니다. 예를 들어, onboarding 기능이 dashboard 기능을 임포트하는 것을 막으려면 다음과 같이 설정할 수 있습니다. + +```json +{ + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["**/features/dashboard/**"], + "message": "Dashboard feature cannot be imported by other features." + } + ] + } + ] + } +} +``` + +**고급 플러그인: eslint-plugin-boundaries를 배워봅시다**: 더 복잡하고 정교한 경계 규칙을 위해서는 eslint-plugin-boundaries와 같은 전문 플러그인을 사용하는 것이 좋다는 것을 알게 됩니다. 이 플러그인은 파일 경로를 기반으로 '요소 타입'(예: feature, component, util)을 정의하고, 어떤 타입이 다른 어떤 타입을 임포트할 수 있는지에 대한 허용/금지 규칙을 상세하게 설정할 수 있게 해줍니다. 이는 FSD와 같은 계층적 아키텍처의 의존성 규칙을 코드로 강제하는 데 매우 유용합니다. + +**FSD 전용 플러그인을 알아봅시다**: FSD 아키텍처를 채택한 팀을 위해, 경로 검사, 공개 API 강제, 계층 간 임포트 규칙 등을 사전 정의된 규칙으로 제공하는 eslint-plugin-fsd-projects나 eslint-plugin-feature-sliced-design-imports와 같은 전용 ESLint 플러그인도 존재한다는 것을 배웁니다. + +이처럼 현대적인 아키텍처 전략은 단순히 폴더 구조를 정의하는 것에서 그치지 않는다는 것을 이해하게 됩니다. 린터와 코드 생성기 같은 도구들은 아키텍처의 '강제 계층(enforcement layer)'을 형성하여, 설계 원칙이 시간이 지나도 무너지지 않도록 보장합니다. 과거에는 아키텍처가 다이어그램으로 문서화되고 코드 리뷰를 통해 수동으로 검증되었다면, 이제는 도구를 통해 개발자의 IDE에서 즉각적인 피드백을 제공하고, 잘못된 구조가 생성되는 것 자체를 방지하는 방식으로 진화했다는 것을 배우게 됩니다. 따라서 2025년의 성공적인 아키텍처 전략은 반드시 그에 상응하는 도구 전략을 포함해야 한다는 중요한 교훈을 얻게 됩니다. + +## Part 5: 의사결정 프레임워크 및 권장사항 + +지금까지 다양한 아키텍처 원칙, 방법론, 프레임워크별 구현 방안을 배워보았습니다. 이 파트에서는 이 모든 학습 내용을 종합하여, 각 프로젝트의 특성에 맞는 최적의 폴더 구조를 선택할 수 있도록 돕는 실용적인 의사결정 프레임워크를 익혀보겠습니다. + +### 5.1. 비교 분석 및 장단점 + +각 아키텍처 접근법의 핵심적인 특징과 장단점을 한눈에 비교할 수 있도록 정리한 표를 살펴봅시다. 이 표는 기술 리더나 아키텍트가 프로젝트의 규모, 복잡성, 팀의 경험 수준과 같은 맥락을 고려하여 전략적인 결정을 내리는 데 유용한 도구가 될 것입니다. + +| 접근법 | 주요 초점 | 이상적인 프로젝트 규모 | 학습 곡선 | ✅ 장점 | ❌ 단점 | +| -------------------------- | --------------------- | ----------------------------- | ----------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| 플랫 / 타입 기반 | 단순성, 속도 | 소규모 / 프로토타입 | 매우 낮음 | 시작이 빠르고 보일러플레이트가 적음. | 확장이 어렵고 응집도가 낮아 유지보수가 힘듦. | +| 기능 기반 | 응집도, 유지보수성 | 중규모 ~ 대규모 | 낮음 ~ 중간 | 높은 응집도, 기능 단위 탐색 및 리팩토링 용이. | 규칙 없이는 비일관적으로 변질될 수 있으며, '기능'과 '공유'의 경계가 모호해질 수 있음. | +| 아토믹 디자인 | UI 재사용성, 일관성 | 모든 규모 (컴포넌트 전략으로) | 중간 | 디자인 시스템 구축에 탁월하며 일관성을 증진시킴. | 너무 세분화될 수 있으며, 자체적으로 비즈니스 로직 구성을 해결하지 못함. | +| DDD 기반 | 비즈니스 로직 정렬 | 대규모 / 복잡함 | 높음 | 코드베이스가 비즈니스 도메인과 일치하여 도메인 전문가에게 직관적임. | 깊은 비즈니스 이해가 필요하며, 잘못 적용될 위험이 있음. | +| 피처 슬라이스 디자인 (FSD) | 엄격한 확장성, 안정성 | 초대규모 / 엔터프라이즈 | 매우 높음 | 극도로 확장 가능하며, 아키텍처 부패를 방지하고 안전한 리팩토링을 보장함. | 가파른 학습 곡선, 높은 보일러플레이트, 소규모 프로젝트에는 과하며, 너무 경직될 수 있음. | + +### 5.2. 2025년을 위한 전략적 권장사항 + +프로젝트의 상황에 따라 다음과 같은 전략적 접근을 권장합니다. 각 상황에 맞는 최적의 선택을 배워봅시다. + +**스타트업 및 MVP (Minimum Viable Product)**: +단순한 기능 기반(Feature-Based) 구조로 시작하는 것이 좋다는 것을 배웁니다. FSD와 같은 복잡한 방법론의 오버헤드를 피하고 개발 속도에 집중하되, 향후 확장을 위한 기초를 다지는 것이 중요합니다. 처음부터 `/features` 폴더를 만들어 비즈니스 로직을 분리하고, 진정으로 범용적인 코드만을 위한 `/shared/components`와 `/shared/hooks` 폴더를 운영하는 것이 좋은 출발점입니다. + +**중규모 성장 단계의 제품**: +견고한 기능 기반 구조나, 비즈니스 로직이 복잡하다면 DDD 기반 구조가 이상적이라는 것을 알게 됩니다. 이 단계에서는 팀이 성장함에 따라 아키텍처의 일관성을 유지하기 위해 자동화된 규칙을 도입해야 합니다. ESLint 규칙을 설정하여 모듈 간의 부적절한 의존성을 차단하고, Plop.js와 같은 코드 생성기를 도입하여 새로운 기능이나 컴포넌트가 정해진 구조를 따르도록 강제하는 것을 고려해야 한다는 것을 배웁니다. + +**대규모 엔터프라이즈 플랫폼 및 다수 팀**: +이것이 바로 **피처 슬라이스 디자인(FSD)과 모노레포 조합이 가장 빛을 발하는 시나리오**라는 것을 이해하게 됩니다. FSD의 엄격함은 더 이상 단점이 아니라, 여러 팀에 걸쳐 장기간에 걸쳐 일관성을 강제하는 강력한 장점이 됩니다. 장기적인 안정성과 유지보수성을 통해 얻는 이점이 크기 때문에, 도구 도입과 팀 교육에 대한 초기 투자는 충분히 정당화됩니다. + +**디자인 시스템 구축**: +아키텍처의 중심은 아토믹 디자인이어야 한다는 것을 배웁니다. 일반적으로 모노레포 내의 packages/ui와 같은 독립된 패키지에서 개발되며, 모노레포의 다른 apps들이 이 디자인 시스템 패키지를 소비하는 형태가 됩니다. + +**황금률 (The Golden Rule)을 기억하세요**: +React 공식 문서의 "폴더 구조에 5분 이상 고민하지 말라"는 조언은 프로토타입 단계에서는 유효할 수 있습니다. 하지만 실제 장기적인 프로젝트를 위한 진정한 조언은 다음과 같다는 것을 배우게 됩니다: + +> "기능 기반 구조로 시작하고, 그 구조가 없어서 겪는 고통이 새로운 구조를 도입하는 비용보다 커질 때에만 점진적으로 복잡성(더 엄격한 계층, 자동화 도구 등)을 추가하라." + +## 끝으로… + +많은 개발자들이 저마다의 철학을 바탕으로 시행착오를 겪고 있고 그 시행착오들 가운데 더 좋다고 생각하는 것들이 모범사례가 되고 이러한 사례를 학습하고 적용해봅니다. 이러한 모법사례가 언제나 지금의 프로젝트에 딱 맞는 옷은 아니기에 우리는 그 위에 다른 자신만의 철학과 시행착오의 변주를 계속 하게 되는 역사가 반복됩니다. + +모범사례의 학습하고 이 방식이 모범사례가 되는 배경을 이해하고 그 배경에 맞게 나의 맥락에 맞게 적용을 하면서 좋은 점과 아쉬운점을 반영하되 아키텍쳐란 함께 하는 것인 만큼 너무 급진적이지 않게 점진적이고 실용적인 접근 방식이 조금씩 키워나가는 시각을 가지게 되기를 바랍니다. + +프로젝트의 성장에 맞춰 아키텍처를 진화시키는 것이 핵심이며, 처음부터 모든 것을 완벽하게 설계하려는 시도는 오히려 개발 속도를 저해하고 불필요한 복잡성만 낳을 수 있다는 것을 기억해주세요. + +모두 모두 화이팅입니다! diff --git a/docs/3-11-Tips for Assignments.md b/docs/3-11-Tips for Assignments.md new file mode 100644 index 000000000..363115662 --- /dev/null +++ b/docs/3-11-Tips for Assignments.md @@ -0,0 +1,481 @@ +# 3-11. 과제를 위한 팁 + +## 과제를 위한 팁 + +### 1. 컴포넌트 분리를 하기 위해서 상태관리는 Context외에도 다양하게 사용할 수 있습니다. + +jotai, zustand 등도 함께 고려해보세요. + +현재 가장 주목받고 있는 상태관리는 zustand입니다. Redux의 한계에서 새로운 상태관리 대안을 고민하는 과정에서 Redux와 가장 유사하면서 세련되어진 zustand가 많이 사용되었습니다. zustand가 익숙한 사람들은 zustand로 도전을 해보세요. + +**상태관리 자체가 아직 익숙지 않은 사람들에게는 zustand보다는 jotai를 사용해서 입문해보기를 권합니다. (과제도요)** jotai는 recoil과 같은 atom형 전역상태관리이며 zustand를 만든 동일한 개발자에 의해 먼저 제안된 상태관리 라이브러리 입니다. 기존의 useState와 사용하는 문법과 표현을 거의 그대로 사용할 수 있도록 만들었기에 자연스럽게 넘어가기에 좋습니다. zustand는 처음부터 zustand스럽게 만들어야 되는게 있어요. + +ex) + +```ts +const [posts, setPosts] = useState([]) +const [selectedPost, setSelectedPost] = useState(null) + +const postsAtom = atom([]) + +const usePost = () => { + const [posts, setPosts] = useAtom(postsAtom); + + const addPost = (post) => setPosts((posts) => xx.addPost(posts, post)) + + return { + posts, + setPosts, + addPost + } +} + +function Component1() { + const {posts, setPosts} = usePost() + const [selectedPost, setSelectedPost] = useState(null) +} + +function Component2() { + const {posts, setPosts} = usePost() + const [selectedPost, setSelectedPost] = useState(null) +} + +// pure +const addPost = posts => [...post, post] +const post = usePost() + +function handlerAddpost(e) { + e.value + + const post = { + title: e.value + } + + if (validate) { + setPosts(posts => [posts, ...post]) + post.addPost(post) + } +} + +... +// features/post/model + +import { atom, useAtom } from "jotai" + +const postsAtom = atom([]) +const selectedPostAtom = atom(null) + +export const usePost = () => { + const [posts, setPosts] = useAtom(postsAtom) + const [selectedPost, setSelectedPost] = useAtom(selectedPostAtom) + + // @TODO: addPost + // @TODO: removePost + // @TODO: searchPost + + return new (class { + posts = posts + setPosts = setPosts + selectedPost = selectedPost + setSelectedPost = setSelectedPost + })() +} +``` + +### 2. FSD 경계에 대해서 고민이 된다면? + +ps. 어디까지나 저의 주관적 해석입니다. 달라질 수 있습니다. 일관성이 중요하니까 나중에 아니다 싶어도 폴더이동이 제일 쉬운 리팩토링입니다. 일단 쪼개놓으면 정돈이야 쉽죠? + +```plaintext +// pure +entitiy + ui = readonly component +entitiy + api = fetch, axios +entitiy + model = type + computational function(=pure function) 2-2 + +// React Layer +features + ui = component + 1 handler +features + api = Tanstack(=useQuery, useMutation), API Store +features + model = useCustomHook + +// UI 말고 Component들 +widgets + ui = component without handler! +widgets + api = widgets 전용 api ex) main banner, features +widgets + model = 잘 안쓰임 + +// 그 Page! +pages + ui = Page! +pages + api = page 전용 api ex) main banner, features +pages + model = Route + + +관련된것은 가까이 두어야 합니다. + +처음부터 나누려고 하지말고, 그냥 Page에서 시작하고 +api면 api폴더에 +ui면 ui폴더에 +model이면 model폴더에 +만들어두시다가 +내려가야 될때가 오면 그때 내려보내세요. + +// 가이드입니다. 일관성 있는 나만의 핵심 Rule을 만들어보세요 :) +``` + +### 3. 의존, 그리고 책임과 범위 - 어떤 Props는 받아야 하고 어떤 Props는 받지 말까? + +어떤 Props는 외부로 부터 받아야 하고 어떤 기능은 컴포넌트에게 책임을 가져야 하는지 잘 생각해보세요. + +단일책임원칙에서 책임을 이해하는 것은 중요합니다. + +외부로 부터 import하거나 props를 받는 것은 위임이고 +내가 처리하는 것은 나의 책임입니다. + +이를 이해하면 단방향 의존성과 단일책임을 가지는 멋진 경계를 만들 수 있습니다. + +--- + +# [부록] Teo’s FSD Convention Guidelines + +## 1. Standard Roles in Each Slice + +```ts +slice/ + ├── ui/ // Visual components + ├── model/ // Business logic, types, state + ├── api/ // Data fetching + ├── lib/ // Utils, helpers + └── config/ // Constants, configurations + +``` + +## 2. Layer-Specific Conventions + +### Entity Layer + +```tsx +entities/product/ + ui/ // Read-only presentational components + api/ // axios, fetch Basic CRUD operations + model/ // Pure business logic + types + lib/ // Entity-specific utilities ex) removeItem, + config/ // Entity configurations ex) MAX_POST_LENGTH + +``` + +**Rules:** + +- All functions must be pure (same input = same output) +- No side effects +- No state management +- No external dependencies +- No API calls within functions +- Focused on data transformation with own scheme +- Reusable across different features +- Testable with pure unit tests +- `ui`: Pure presentational components +- `model`: Contains pure functions, business logic, and type definitions + +**Key Point: pure!** + +**Example:** + +```ts +// ✅ Good +// entities/product/model/validation.ts +export const isValidProduct = (product: Product): boolean => { + return product.price > 0 && product.stock >= 0 +} + +// ❌ Bad +// Impure function with side effects +export const updateProductPrice = (product: Product, newPrice: number) => { + product.price = newPrice // Mutation! + api.updateProduct(product) // Side effect! +} +``` + +--- + +### Features Layer + +**Components to Include:** + +1. Main action component +2. All related form inputs +3. All supporting UI elements + +**Rules** + +1. **One feature = One primary user action** + - Include **one primary user action** + - Include all supporting UI elements + - All related form inputs belong together +2. **Hook-Based Logic** + - Business logic lives in hooks + - Each hook has single responsibility +3. **Props Guidelines** + - Accept only domain data + - No event handler props + - No UI configuration props + +### Examples + +### ✅ Good Example + +**Primary user action + Domain Props + hooks** + +```ts +// features/addToCart/ui/AddToCartButton.tsx +export const AddToCartButton = ({ product }: { product: Product }) => { + const { addToCart, isLoading } = useAddToCart(); + + function handleAddToCart(product:Product) { + addToCart(product) + } + + return ( + + ); +} + +// features/addToCart/model/useAddToCart.ts +export const useAddToCart = () => { + const { mutate, isLoading } = useAddToCartMutation(); + + const handleAddToCart = (productId: string) => { + mutate(productId); + }; + + return { handleAddToCart, isLoading }; +}; + +``` + +All related form inputs + All supporting UI elements + +```ts +// ✅ Good Example 2: Action with Input State +// features/addComment/ui/AddCommentForm.tsx +export const AddCommentForm = ({ productId }: { productId: string }) => { + const [comment, setComment] = useState(""); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!comment.trim()) return; + + api.addComment(productId, comment); + setComment(""); + }; + + return ( +
+