[poompoom] FSD 디렉토리 구조 적용 중, 역할 기준 폴더에 관하여
✅ atoms/, molecules/, organisms/ 구조의 배경
이건 Atomic Design 패턴에 기반한 UI 계층 구조입니다.
atoms | 가장 작은 단위의 UI | Button, Input, Label |
molecules | 2개 이상의 atom 조합 | Input + Label |
organisms | 섹션 단위 구성 요소 | Header, Footer, Form |
➡️ 이 구조는 UI 재사용성, 디자인 시스템 구성에 유리하지만
➡️ 실제 비즈니스 로직과 도메인 흐름과는 분리되어 있어서, 복잡해질 수 있습니다.
✅ FSD에서 이걸 어떻게 해결하나?
FSD는 UI 계층보다 **도메인 관심사(domain-centric)**를 기준으로 나눕니다.
범위위치예시
특정 feature 안에서만 쓰임 | features/review/ui/Button.tsx | 도메인 내부 |
여러 feature에서 반복 사용 | shared/ui/Button.tsx | 재사용성 있음 |
단순 스타일 컴포넌트 | shared/ui/atoms/ (있어도 되지만 권장되진 않음) | 디자인 시스템용 특수 목적 |
✅ **FSD(Folder Structure Design)**에서 디자인 시스템(Design System)을 구성할 때의 컴포넌트 관리 전략은 다음 두 가지 관점을 균형 있게 반영하는 것이 핵심입니다:
- 디자인 시스템의 Atomic Design 원칙 (atoms → molecules → organisms)
- FSD의 관심사 분리 원칙 (features, widgets, shared)
✅ 구성 전략 핵심 요약
재사용 가능한 최소 UI 단위 | shared/ui/ | Button, Input, Tag 등 → 어디서나 쓰이는 “디자인 시스템 기반 컴포넌트” |
섹션 단위 UI 블록 | widgets/ | Header, Footer, Card 등 → 복합 UI 조각 |
도메인에 특화된 UI | features/xxx/ui/ | 도메인 전용 UI (ex. ReviewForm, JoinStepper 등) |
✅ FSD + 디자인 시스템 연계 전략
Atomic 단위 컴포넌트 | shared/ui/ | 디자인 시스템에서 정의한 스타일 시스템 기반으로 설계 |
Tokens, Theme | shared/theme/, shared/styles/ | 색상, 타이포그래피, spacing 등 전역 변수화 (디자인 토큰) |
Storybook | shared/ui/__stories__ | 디자인 시스템 문서화 및 팀 공유 용도 |
✅ 디자인 시스템 도입 시 주의할 점
- ❌ 공통 컴포넌트를 무조건 components/에 넣지 말 것 → FSD 원칙 위반
- ✅ UI 역할/범용성 기준으로 shared/ui ↔ widgets ↔ features/ui로 계층 나눌 것
- ✅ 변경 가능성이 있는 부분(사이즈, 색상, radius)은 theme token으로 분리
-----
❌ 왜 constants/는 FSD에 어울리지 않나?
- constants/는 **역할 기준 분리 (Role-based 구조)**이지만,
- FSD는 기능 단위(feature-based) 분리가 원칙입니다.
- 따라서 constants/처럼 단일 폴더에 모든 초기 상태를 몰아넣으면:
- 도메인 간 결합도 증가
- 유지보수 어려움
- 리팩토링/삭제 범위 파악 어려움
✅ 역할 기준 분리 vs 기능 기준 분리
역할 기준 분리(Role-based structure):
→ **"이 파일이 무슨 역할을 하는가?"**에 따라 디렉토리를 나눔기능 기준 분리(Feature-based structure, FSD):
→ **"이 파일이 어떤 기능(도메인)을 위한 것인가?"**에 따라 디렉토리를 나눔
✅ 예시로 비교
📦 1. 역할 기준 분리 (Role-based structure)
src/
├── features/
│ ├── login/
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
│ ├── review/
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
├── shared/
- components/에는 모든 UI 컴포넌트
- hooks/에는 모든 커스텀 훅
- constants/에는 모든 상수
- → 파일의 “역할”에 따라 그룹화
❌ 단점:
- 기능이 흩어짐 → 한 기능의 코드가 여러 폴더에 분산
- 유지보수 어려움, 협업 중 충돌 빈도 증가
📦 2. 기능 기준 분리 (Feature-based structure, FSD)
src/
├── features/
│ ├── login/
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
│ ├── review/
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
├── shared/
- login/이라는 도메인 아래에 model, api, ui 모두 있음
- → 파일의 “기능(도메인)”에 따라 그룹화
✅ 장점:
- 관련 파일이 한 폴더에 모여 있어 찾기 쉽고, 유지보수 편함
- 팀원 간 책임이 명확해짐 (ex. "리뷰는 네가 맡아")
✅ 요약
기준 | "무슨 역할?" | "어떤 기능?" |
폴더명 | components, hooks, constants 등 | login, review, subscription 등 |
장점 | 초기에 간단하게 시작 가능 | 확장성, 유지보수, 협업에 강함 |
단점 | 대형 프로젝트에서 확산/혼란 발생 | 처음 설계 시 약간 번거로움 |
=> 그래서 FSD에서는…
- constants/initialProfile.js 같은 건
→ ❌ “상수니까 constants”
→ ✅ “프로필 기능이니까 features/auth/model”
features/review/에는 리뷰 도메인의 "기능 흐름" 전체, 즉 CRUD, 상세 페이지, 전체 리스트(무한스크롤 포함) 모두 들어가는 것이 FSD 구조상 올바릅니다.
✅ 왜 다 features/review 안에 포함하는가?
FSD에서 features/는 **"비즈니스 도메인 단위로 로직과 UI를 수직적으로 응집"**합니다.
리뷰와 관련된 모든 기능은 하나의 도메인(review) 아래로 모이는 것이 기본 원칙입니다.
✅ 포함되는 기능 예시
features/
└── review/
├── ui/
│ ├── ReviewForm.tsx ← 리뷰 작성
│ ├── ReviewDetail.tsx ← 상세 조회
│ ├── ReviewList.tsx ← 전체 목록 (무한스크롤 포함)
│ └── ReviewCard.tsx ← 리스트 내 카드 단위
├── api/
│ ├── getReviews.ts ← GET /reviews
│ ├── getReviewById.ts
│ ├── createReview.ts
│ └── deleteReview.ts
├── model/
│ ├── reviewSlice.ts ← 상태 관리
│ ├── reviewTypes.ts
│ └── useReviewForm.ts
✅ 분리하면 안 되는 구조 예시 ❌
features/
├── review-create/
├── review-detail/
├── review-list/
- ❌ 이렇게 기능별로 쪼개면 도메인 단위 응집력이 깨지고 유지보수 힘들어짐
- ❌ 상태, 타입, API, UI가 중복됨 → 확장성 낮음
주요 기능 흐름이란?
: 사용자가 앱을 사용하는 목적을 달성하기 위해 거치는 실제 상호작용의 핵심 단계들을 말합니다.
→ 즉, **사용자 행동의 "비즈니스적인 핵심 경로"**를 구성하는 흐름입니다.
✅ 기능 흐름이란?
쇼핑몰 | 로그인 → 상품 검색 → 장바구니 → 결제 |
소셜 앱 | 회원가입 → 피드 조회 → 좋아요 → 댓글 |
콘텐츠 앱 | 카테고리 선택 → 콘텐츠 조회 → 북마크 저장 |
💡 이런 단계들은 대부분 features/ 폴더에 해당하는 "도메인 기능 단위"로 분리됩니다.
✅ 그럼 메인 배너, 슬라이더 같은 건?
- 메인 배너는 UI적으로 항상 보이지만, 사용자의 목적 달성에 직접적인 기능은 수행하지 않음
- 따라서 보통은 기능 흐름에는 포함되지 않으며, widget/ 또는 shared/ui로 분류
메인 배너 슬라이더 | ❌ (핵심 기능이 아님) | widgets/BannerSlider |
로그인 폼 | ✅ | features/auth/ui/LoginForm |
상품 결제 폼 | ✅ | features/payment/ui/PaymentForm |
푸터 | ❌ | widgets/Footer |
검색 결과 리스트 | ✅ | features/search/ui/SearchResults |
✅ 정리
기능 흐름이란? | 사용자가 앱의 주 기능을 이용하는 과정 (도메인 중심) |
포함 예시 | 로그인, 회원가입, 리뷰 등록, 결제 등 |
포함되지 않는 것 | 메인 배너, 푸터, 소개 섹션 등 단순 UI 영역 |
분리 기준 | 기능 중심이면 features/, UI 장식/공통이면 widgets/ 또는 shared/ui/ |
React에서 폴더/파일 이름은 팀 컨벤션에 따라 달라질 수 있지만, 아래와 같은 일반적인 컨벤션이 많이 사용됩니다:
✅ 표준 컨벤션 요약
✅ 컴포넌트 파일 | 파스칼 케이스 | LoginForm.tsx, UserCard.tsx |
✅ 폴더명 | 케밥 케이스 또는 소문자 카멜케이스 | user-profile/, review-form/ |
✅ 훅 파일 | 카멜 케이스 | useLogin.ts, useInfiniteScroll.ts |
✅ 일반 유틸/상수 파일 | 카멜 케이스 | apiClient.ts, dateFormatter.ts |
🔍 폴더 vs 파일명 예시
shared/
└── ui/
├── button/ ← ✅ 케밥 케이스 폴더
│ └── Button.tsx ← ✅ 파스칼 케이스 컴포넌트
├── input/
│ └── Input.tsx
hooks/
└── useLogin.ts ← ✅ 카멜 케이스 훅
📌 왜 이렇게 쓰는가?
컴포넌트는 클래스처럼 보이므로 파스칼 | LoginForm, UserCard |
폴더는 경로로 사용되므로 케밥이 가독성 ↑ | user-profile, review-card |
훅/유틸은 함수 느낌을 주기 위해 카멜 | useAuth, apiClient |
❗ 지양하는 패턴
loginform.tsx | 가독성 낮음, 컴포넌트처럼 안 보임 |
UseLogin.ts | 훅은 함수처럼 보여야 하므로 ❌ |
ReviewCard.tsx in ReviewCard/ReviewCard.tsx | 중복 → review-card/ReviewCard.tsx 또는 index.tsx 추천 |
✅ 정리
컴포넌트 파일 | 파스칼 케이스 | LoginForm.tsx |
폴더 | 케밥 케이스 권장 | login-form/, review-card/ |
훅 | 카멜 케이스 | useScroll.ts |
유틸 | 카멜 케이스 | apiClient.ts, formatDate.ts |