프로젝트

[poompoom] FSD 디렉토리 구조 적용 중, 역할 기준 폴더에 관하여

dekoms 2025. 5. 9. 02:58
 

 

✅ 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)을 구성할 때의 컴포넌트 관리 전략은 다음 두 가지 관점을 균형 있게 반영하는 것이 핵심입니다:

  1. 디자인 시스템의 Atomic Design 원칙 (atoms → molecules → organisms)
  2. 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. "리뷰는 네가 맡아")

✅ 요약

구분역할 기준 구조기능 기준 구조 (FSD)
기준 "무슨 역할?" "어떤 기능?"
폴더명 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로 분류
컴포넌트기능 흐름 포함 여부FSD 위치
메인 배너 슬라이더 ❌ (핵심 기능이 아님) 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