๐ FSD(Feature-Sliced Design)๋?
ํ๋ก ํธ์๋ ํ๋ก์ ํธ์ ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก ์ฝ๋๋ฒ ์ด์ค์ ๋ณต์ก๋๋ ๊ธฐํ๊ธ์์ ์ผ๋ก ์ฆ๊ฐํฉ๋๋ค. "์ด ์ปดํฌ๋ํธ๋ ์ด๋์ ์์ง?", "์ด ๊ธฐ๋ฅ์ ์์ ํ๋ฉด ์ด๋๊ฐ ์ํฅ๋ฐ์๊น?", "์๋ก์ด ํ์์ด ํ๋ก์ ํธ ๊ตฌ์กฐ๋ฅผ ์ดํดํ๋๋ฐ ์ผ๋ง๋ ๊ฑธ๋ฆด๊น?" ๊ฐ์ ๊ณ ๋ฏผ์ด ๋์ด์ง ์์ต๋๋ค.
Feature-Sliced Design(FSD)์ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ํ์ํ ํ๋ก ํธ์๋ ์ํคํ ์ฒ ๋ฐฉ๋ฒ๋ก ์ ๋๋ค. ์ง์ญํ๋ฉด '๊ธฐ๋ฅ ๋ถํ ์ค๊ณ'๋ก, ์ฝ๋๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ๊ตฌ์ฑํ๊ธฐ ์ํ ๊ท์น๊ณผ ๊ด๋ก์ ์งํฉ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.

๐ฏ FSD๊ฐ ํด๊ฒฐํ๋ ๋ฌธ์
๊ธฐ์กด ํ๋ก ํธ์๋ ํ๋ก์ ํธ์ ๊ณ ์ง์ ์ธ ๋ฌธ์ ๋ค
- ๋ถ๋ช
ํํ ์ฝ๋ ๊ตฌ์กฐ ๐
- ์ปดํฌ๋ํธ, ํ , ์ ํธ๋ฆฌํฐ๊ฐ ๋ค์์ฌ ์์ด ์ฐพ๊ธฐ ์ด๋ ค์
- ๊ฐ์์ ์คํ์ผ๋ก ํด๋๋ฅผ ๊ตฌ์ฑํด ์ผ๊ด์ฑ ๋ถ์กฑ
- ๋์ ๊ฒฐํฉ๋ ๐
- ํ ๊ธฐ๋ฅ์ ์์ ํ๋ฉด ์์์น ๋ชปํ ๊ณณ์์ ๋ฒ๊ทธ ๋ฐ์
- ์ฝ๋ ๊ฐ ์์กด์ฑ์ด ๋ณต์กํ๊ฒ ์ฝํ์์
- ๋ฎ์ ์ฌ์ฌ์ฉ์ฑ โป๏ธ
- ๋น์ทํ ๊ธฐ๋ฅ์ ์ฌ๋ฌ ๊ณณ์์ ์ค๋ณต์ผ๋ก ๊ตฌํ
- ๊ณตํต ๋ก์ง์ ์ถ์ถํ๊ธฐ ์ด๋ ค์
- ํ์ฅ์ ์ด๋ ค์ ๐
- ์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋๋ง๋ค ๊ธฐ์กด ๊ตฌ์กฐ์ ์ถฉ๋
- ๋ฆฌํฉํ ๋ง์ด ๋๋ ค์ ๊ธฐ์ ๋ถ์ฑ๊ฐ ์์
FSD๋ ์ด ๋ชจ๋ ๋ฌธ์ ๋ฅผ ๊ณ์ธตํ๋ ๊ตฌ์กฐ์ ๋ช ํํ ์์กด์ฑ ๊ท์น์ผ๋ก ํด๊ฒฐํฉ๋๋ค.
๐๏ธ FSD์ ํต์ฌ ๊ตฌ์กฐ: 3๋จ๊ณ ๊ณ์ธต
FSD๋ ํ๋ก์ ํธ๋ฅผ Layer(๋ ์ด์ด) → Slice(์ฌ๋ผ์ด์ค) → Segment(์ธ๊ทธ๋จผํธ)์ 3๋จ๊ณ๋ก ๊ตฌ์กฐํํฉ๋๋ค.
๐ฆ ํ๋ก์ ํธ
โโโ ๐ Layer (๋ ์ด์ด) - ์ต์์ ๊ณ์ธต
โ โโโ ๐ Slice (์ฌ๋ผ์ด์ค) - ๋น์ฆ๋์ค ๋๋ฉ์ธ๋ณ ๋ถ๋ฆฌ
โ โ โโโ ๐ Segment (์ธ๊ทธ๋จผํธ) - ๊ธฐ๋ฅ๋ณ ๋ถ๋ฆฌ
โ โ โ โโโ ๐ ํ์ผ๋ค
๐ Layer(๋ ์ด์ด): 7๊ฐ์ง ๊ณ์ธต์ ์ธ๊ณ
FSD๋ ์ด 7๊ฐ์ ๋ ์ด์ด๋ฅผ ์ ์ํ์ง๋ง, ์ค์ ๋ก๋ 6๊ฐ๋ง ์ฌ์ฉํฉ๋๋ค. (Processes ๋ ์ด์ด๋ ํ์ฌ ์ฌ์ฉ๋์ง ์์)
๊ณ์ธต ๊ตฌ์กฐ ํ๋์ ๋ณด๊ธฐ
โฌ๏ธ ์์ ๋ ์ด์ด (์ฌ์ฉ์์ ๊ฐ๊น์)
โ
โโโโโโดโโโโโ
โ App โ ๐ ์ ํ๋ฆฌ์ผ์ด์
์ง์
์ & ์ ์ญ ์ค์
โโโโโโฌโโโโโ
โ
โโโโโโดโโโโโ
โ Pages โ ๐ ๊ฐ๋ณ ํ์ด์ง (๋ผ์ฐํธ ๋จ์)
โโโโโโฌโโโโโ
โ
โโโโโโดโโโโโ
โ Widgets โ ๐งฉ ๋
๋ฆฝ์ ์ธ UI ๋ธ๋ก (์ฌ์ฌ์ฉ ๊ฐ๋ฅ)
โโโโโโฌโโโโโ
โ
โโโโโโดโโโโโโ
โ Features โ โก ๋น์ฆ๋์ค ๊ธฐ๋ฅ (์ฌ์ฉ์ ํ๋)
โโโโโโฌโโโโโโ
โ
โโโโโโดโโโโโโ
โ Entities โ ๐ฆ ๋น์ฆ๋์ค ์ํฐํฐ (๋ฐ์ดํฐ ๋ชจ๋ธ)
โโโโโโฌโโโโโโ
โ
โโโโโโดโโโโโ
โ Shared โ ๐ง ๊ณตํต ๋ฆฌ์์ค (UI ์ปดํฌ๋ํธ, ์ ํธ)
โโโโโโโโโโโ
โ
โฌ๏ธ ํ์ ๋ ์ด์ด (๊ธฐ์ ์ ๊ธฐ๋ฐ)
1๏ธโฃ App Layer (์ ํ๋ฆฌ์ผ์ด์ )
์ญํ : ํ๋ก์ ํธ์ ์ง์ ์ ์ด์ ์ ์ญ ์ค์ ์ ๊ด๋ฆฌํ๋ ์ต์์ ๋ ์ด์ด
ํฌํจ ์์:
- ๋ผ์ฐํ ์ค์ ๐ฃ๏ธ
- ์ ์ญ ์คํ์ผ ๐จ
- Provider (Redux, Theme ๋ฑ) ๐
- ์ ์ญ ์ํ ์ด๊ธฐํ โ๏ธ
ํน์ง: ์ฌ๋ผ์ด์ค ์์ด ์ธ๊ทธ๋จผํธ๋ง ํฌํจ
๐ app/
โโโ ๐ providers/ // Redux Provider, Theme Provider
โโโ ๐ styles/ // Global CSS, Reset CSS
โโโ ๐ router/ // React Router ์ค์
โโโ ๐ index.tsx // ์ฑ ์ง์
์
์ค์ ์์:
// app/index.tsx
import { ThemeProvider } from '@/app/providers/theme'
import { Router } from '@/app/router'
export const App = () => (
<ThemeProvider>
<Router />
</ThemeProvider>
)
2๏ธโฃ Pages Layer (ํ์ด์ง)
์ญํ : URL ๋ผ์ฐํธ์ ๋งคํ๋๋ ๊ฐ๋ณ ํ์ด์ง๋ฅผ ๊ตฌ์ฑ
ํน์ง:
- ๊ฐ ํ์ด์ง๋ ํ๋์ ์ฌ๋ผ์ด์ค
- Widgets์ Features๋ฅผ ์กฐํฉํด ์์ฑ๋ ํ์ด์ง ๊ตฌ์ฑ
- ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง๋ณด๋ค๋ UI ์กฐํฉ์ ์ง์ค
๐ pages/
โโโ ๐ home/ // ํ ํ์ด์ง ("/")
โ โโโ ๐ ui/
โ โโโ ๐ index.ts
โโโ ๐ user-profile/ // ํ๋กํ ํ์ด์ง ("/profile/:id")
โ โโโ ๐ ui/
โ โโโ ๐ index.ts
โโโ ๐ settings/ // ์ค์ ํ์ด์ง ("/settings")
โโโ ๐ ui/
โโโ ๐ index.ts
์ค์ ์์:
// pages/user-profile/ui/UserProfilePage.tsx
import { UserHeader } from '@/widgets/user-header'
import { UserPosts } from '@/widgets/user-posts'
import { FollowButton } from '@/features/follow-user'
export const UserProfilePage = () => (
<div>
<UserHeader />
<FollowButton />
<UserPosts />
</div>
)
3๏ธโฃ Widgets Layer (์์ ฏ)
์ญํ : ๋ ๋ฆฝ์ ์ผ๋ก ์๋ํ๋ ํฐ UI ๋ธ๋ก
ํน์ง:
- ์ฌ๋ฌ Features์ Entities๋ฅผ ์กฐํฉ
- ๋ค์ํ ํ์ด์ง์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
- ์์ฒด์ ์ผ๋ก ์๊ฒฐ๋ ๊ธฐ๋ฅ ์ ๊ณต
์์:
- ๊ฒ์๋ฐ (SearchBar) ๐
- ํค๋/๋ค๋น๊ฒ์ด์ (Header, Sidebar) ๐
- ๋๊ธ ์น์ (CommentSection) ๐ฌ
- ๋์๋ณด๋ ์นด๋ (DashboardCard) ๐
๐ widgets/
โโโ ๐ user-header/ // ์ฌ์ฉ์ ํค๋ ์์ ฏ
โ โโโ ๐ ui/
โ โ โโโ UserHeader.tsx
โ โโโ ๐ model/
โ โโโ ๐ index.ts
โโโ ๐ post-feed/ // ํฌ์คํธ ํผ๋ ์์ ฏ
โโโ ๐ ui/
โโโ ๐ model/
โโโ ๐ index.ts
์ค์ ์์:
// widgets/user-header/ui/UserHeader.tsx
import { UserAvatar } from '@/entities/user'
import { FollowButton } from '@/features/follow-user'
import { EditProfileButton } from '@/features/edit-profile'
export const UserHeader = ({ userId }) => (
<header>
<UserAvatar userId={userId} />
<FollowButton userId={userId} />
<EditProfileButton userId={userId} />
</header>
)
4๏ธโฃ Features Layer (๊ธฐ๋ฅ)
์ญํ : ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋น์ฆ๋์ค ๊ธฐ๋ฅ ๋จ์
ํน์ง:
- ์ฌ์ฉ์ ํ๋๊ณผ ์ง์ ์ฐ๊ฒฐ๋ ๊ธฐ๋ฅ
- ๋ ๋ฆฝ์ ์ผ๋ก ๋์ ๊ฐ๋ฅ
- ์ฌ๋ฌ ํ์ด์ง/์์ ฏ์์ ์ฌ์ฌ์ฉ
์์:
- ์ข์์ ๋ฒํผ (LikePost) โค๏ธ
- ํ๋ก์ฐ/์ธํ๋ก์ฐ (FollowUser) ๐ฅ
- ๋๊ธ ์์ฑ (AddComment) ๐ฌ
- ๋ก๊ทธ์ธ/๋ก๊ทธ์์ (AuthUser) ๐
๐ features/
โโโ ๐ follow-user/ // ํ๋ก์ฐ ๊ธฐ๋ฅ
โ โโโ ๐ ui/
โ โ โโโ FollowButton.tsx
โ โโโ ๐ model/ // ํ๋ก์ฐ ์ํ ๊ด๋ฆฌ
โ โโโ ๐ api/ // ํ๋ก์ฐ API ํธ์ถ
โ โโโ ๐ index.ts
โโโ ๐ like-post/ // ์ข์์ ๊ธฐ๋ฅ
โโโ ๐ ui/
โโโ ๐ model/
โโโ ๐ api/
โโโ ๐ index.ts
์ค์ ์์:
// features/follow-user/ui/FollowButton.tsx
import { useFollowUser } from '../model/useFollowUser'
import { Button } from '@/shared/ui/button'
export const FollowButton = ({ userId }) => {
const { isFollowing, toggle } = useFollowUser(userId)
return (
<Button onClick={toggle}>
{isFollowing ? '์ธํ๋ก์ฐ' : 'ํ๋ก์ฐ'}
</Button>
)
}
5๏ธโฃ Entities Layer (์ํฐํฐ)
์ญํ : ๋น์ฆ๋์ค์ ํต์ฌ ๋ฐ์ดํฐ ๋ชจ๋ธ ๊ด๋ฆฌ
ํน์ง:
- ํ๋ก์ ํธ์ ํต์ฌ ๋๋ฉ์ธ ๊ฐ์ฒด
- ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ๊ธฐ๋ณธ ๋ก์ง ํฌํจ
- ์ฌ์ฉ์ ํ๋๊ณผ ๋ฌด๊ดํ ์์ ๋ฐ์ดํฐ ๊ณ์ธต
์์:
- User (์ฌ์ฉ์) ๐ค
- Post (๊ฒ์๋ฌผ) ๐
- Comment (๋๊ธ) ๐ฌ
- Product (์ํ) ๐
๐ entities/
โโโ ๐ user/ // ์ฌ์ฉ์ ์ํฐํฐ
โ โโโ ๐ ui/
โ โ โโโ UserCard.tsx
โ โ โโโ UserAvatar.tsx
โ โโโ ๐ model/ // ํ์
์ ์, ์ํ ๊ด๋ฆฌ
โ โโโ ๐ api/ // ์ฌ์ฉ์ ๋ฐ์ดํฐ fetch
โ โโโ ๐ index.ts
โโโ ๐ post/ // ๊ฒ์๋ฌผ ์ํฐํฐ
โโโ ๐ ui/
โโโ ๐ model/
โโโ ๐ api/
โโโ ๐ index.ts
์ค์ ์์:
// entities/user/model/types.ts
export interface User {
id: string
name: string
email: string
avatar: string
followersCount: number
}
// entities/user/ui/UserCard.tsx
import { User } from '../model/types'
export const UserCard = ({ user }: { user: User }) => (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.followersCount} followers</p>
</div>
)
6๏ธโฃ Shared Layer (๊ณต์ )
์ญํ : ํ๋ก์ ํธ ์ ๋ฐ์์ ์ฌ์ฌ์ฉ๋๋ ๊ณตํต ๋ฆฌ์์ค
ํน์ง:
- ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ฌด๊ดํ ์์ ๊ธฐ์ ๊ณ์ธต
- ์ฌ๋ผ์ด์ค ์์ด ์ธ๊ทธ๋จผํธ๋ง ํฌํจ
- ํ์ํ ๋๋ง๋ค ์ถ๊ฐํ๋ ๋ฐฉ์
ํฌํจ ์์:
- UI ์ปดํฌ๋ํธ (Button, Input, Modal) ๐จ
- ์ ํธ๋ฆฌํฐ ํจ์ (๋ ์ง ํฌ๋งท, ๋ฌธ์์ด ์ฒ๋ฆฌ) ๐ง
- API ํด๋ผ์ด์ธํธ (Axios ์ธ์คํด์ค) ๐
- ์์/์ค์ (ํ๊ฒฝ ๋ณ์, ์ค์ ๊ฐ) โ๏ธ
๐ shared/
โโโ ๐ ui/ // ๊ธฐ๋ณธ UI ์ปดํฌ๋ํธ
โ โโโ button/
โ โโโ input/
โ โโโ modal/
โ โโโ index.ts
โโโ ๐ lib/ // ์ ํธ๋ฆฌํฐ ํจ์
โ โโโ format/
โ โโโ validation/
โ โโโ index.ts
โโโ ๐ api/ // API ํด๋ผ์ด์ธํธ
โ โโโ client.ts
โโโ ๐ config/ // ์์, ์ค์
โโโ constants.ts
์ค์ ์์:
// shared/ui/button/Button.tsx
export const Button = ({ children, variant = 'primary', ...props }) => (
<button
className={`btn btn-${variant}`}
{...props}
>
{children}
</button>
)
// shared/lib/format/date.ts
export const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('ko-KR').format(date)
}
๐ FSD์ ํต์ฌ ์์น: ๋จ๋ฐฉํฅ ์์กด์ฑ
์์กด์ฑ ๊ท์น (Dependency Rule)
FSD์ ๊ฐ์ฅ ์ค์ํ ๊ท์น์ "์์ ๋ ์ด์ด๋ง ํ์ ๋ ์ด์ด์ ์์กดํ ์ ์๋ค"๋ ๊ฒ์ ๋๋ค.
App โโโโโโโโโโโโโโโโโโโโโโโโโโโ
↓ โ
Pages โโโโโโโโโโโโโโโโโโโโโ โ
↓ โ โ
Widgets โโโโโโโโโโโโโโโ โ โ
↓ โ โ โ
Features โโโโโโโโโโโ โ โ โ
↓ โ โ โ โ
Entities โโโโโโโโโ โ โ โ โ
↓ โ โ โ โ โ
Shared ←โโโโโโโโโโดโโดโโโดโโโโดโโโโโ
โ ํ์ฉ๋๋ ์์กด์ฑ
// โ
Pages → Widgets
import { UserHeader } from '@/widgets/user-header'
// โ
Widgets → Features
import { FollowButton } from '@/features/follow-user'
// โ
Features → Entities
import { User } from '@/entities/user'
// โ
Entities → Shared
import { Button } from '@/shared/ui/button'
โ ๊ธ์ง๋๋ ์์กด์ฑ
// โ Entities → Features (ํ์ → ์์)
import { FollowButton } from '@/features/follow-user'
// โ Shared → Entities (ํ์ → ์์)
import { User } from '@/entities/user'
// โ ๊ฐ์ ๋ ์ด์ด์ ๋ค๋ฅธ ์ฌ๋ผ์ด์ค ์ฐธ์กฐ
// features/follow-user → features/like-post
import { LikeButton } from '@/features/like-post'
๐ฏ ์ด ๊ท์น์ ์ฅ์
- ์์ธก ๊ฐ๋ฅํ ์ฝ๋ ํ๋ฆ ๐ฎ
- ์์กด์ฑ์ด ํญ์ ํ ๋ฐฉํฅ์ผ๋ก๋ง ํ๋ฆ
- ์ํ ์ฐธ์กฐ ๋ฐฉ์ง
- ์์ ํ ๋ฆฌํฉํ ๋ง ๐ง
- ํ์ ๋ ์ด์ด ์์ ์ ์์์๋ง ์ํฅ
- ์์ ๋ ์ด์ด ์์ ์ ๋ค๋ฅธ ๋ ์ด์ด์ ๋ฌด์ํฅ
- ๋ช
ํํ ์ฑ
์ ๋ถ๋ฆฌ ๐
- ๊ฐ ๋ ์ด์ด์ ์ญํ ์ด ๋ช ํ
- ์ฝ๋๋ฅผ ์ด๋์ ๋์ง ๊ณ ๋ฏผ ๊ฐ์
๐ Slice(์ฌ๋ผ์ด์ค): ๋น์ฆ๋์ค ๋๋ฉ์ธ๋ณ ๋ถ๋ฆฌ
์ฌ๋ผ์ด์ค๋?
์ฌ๋ผ์ด์ค๋ ๋น์ฆ๋์ค ๋๋ฉ์ธ์ ๊ธฐ์ค์ผ๋ก ์ฝ๋๋ฅผ ๋๋๋ ๋ ๋ฒ์งธ ๊ณ์ธต์ ๋๋ค.
์ฃผ์: App๊ณผ Shared ๋ ์ด์ด๋ ์ฌ๋ผ์ด์ค๋ฅผ ๊ฐ์ง์ง ์์ต๋๋ค!
์ฌ๋ผ์ด์ค ๋ช ๋ช ๊ท์น
// โ
์ข์ ์ฌ๋ผ์ด์ค ์ด๋ฆ (๋น์ฆ๋์ค ๋๋ฉ์ธ)
features/
follow-user/ // ์ฌ์ฉ์ ํ๋ก์ฐ
like-post/ // ๊ฒ์๋ฌผ ์ข์์
edit-profile/ // ํ๋กํ ์์
entities/
user/ // ์ฌ์ฉ์
post/ // ๊ฒ์๋ฌผ
comment/ // ๋๊ธ
// โ ๋์ ์ฌ๋ผ์ด์ค ์ด๋ฆ (๊ธฐ์ ์ ๊ตฌ๋ถ)
features/
api/
components/
hooks/
์ฌ๋ผ์ด์ค ๋ ๋ฆฝ์ฑ ์์น
ํต์ฌ ๊ท์น: ๊ฐ์ ๋ ์ด์ด์ ๋ค๋ฅธ ์ฌ๋ผ์ด์ค๋ฅผ ์ฐธ์กฐํ ์ ์์ต๋๋ค!
// โ ๊ธ์ง: ๊ฐ์ ๋ ์ด์ด ๊ฐ ์ฐธ์กฐ
// features/follow-user/ui/FollowButton.tsx
import { LikeButton } from '@/features/like-post'
// โ
ํ์ฉ: ํ์ ๋ ์ด์ด ์ฐธ์กฐ
// features/follow-user/ui/FollowButton.tsx
import { User } from '@/entities/user'
import { Button } from '@/shared/ui/button'
์ด ๊ท์น์ด ์ค์ํ ์ด์ :
- ๋ฎ์ ๊ฒฐํฉ๋ ๐
- ๊ฐ ์ฌ๋ผ์ด์ค๋ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐ/ํ ์คํธ ๊ฐ๋ฅ
- ํ ์ฌ๋ผ์ด์ค ์์ ์ด ๋ค๋ฅธ ์ฌ๋ผ์ด์ค์ ์ํฅ ์์
- ๋์ ์์ง๋ ๐ฏ
- ๊ด๋ จ๋ ์ฝ๋๋ ํ ์ฌ๋ผ์ด์ค์ ๋ชจ์
- ๊ธฐ๋ฅ๋ณ๋ก ์๊ฒฐ๋ ๊ตฌ์กฐ
- ๋ช
ํํ ๊ฒฝ๊ณ ๐ง
- ๊ฐ ๋น์ฆ๋์ค ๋๋ฉ์ธ์ ๊ฒฝ๊ณ๊ฐ ๋ช ํ
- ์ฝ๋ ์์ ๊ถ์ด ๋ถ๋ช
๐ฆ Segment(์ธ๊ทธ๋จผํธ): ๊ธฐ๋ฅ๋ณ ๋ถ๋ฆฌ
์ธ๊ทธ๋จผํธ๋?
์ธ๊ทธ๋จผํธ๋ ์ฌ๋ผ์ด์ค ๋ด๋ถ๋ฅผ ๊ธฐ๋ฅ์ ์ญํ ๋ก ๋๋๋ ์ธ ๋ฒ์งธ ๊ณ์ธต์ ๋๋ค.
์ผ๋ฐ์ ์ธ ์ธ๊ทธ๋จผํธ ์ข ๋ฅ
๐ user-slice/
โโโ ๐ ui/ // ๐จ UI ์ปดํฌ๋ํธ
โ โโโ UserCard.tsx
โ โโโ UserAvatar.tsx
โโโ ๐ model/ // ๐ ์ํ ๊ด๋ฆฌ & ๋น์ฆ๋์ค ๋ก์ง
โ โโโ types.ts
โ โโโ store.ts
โ โโโ hooks.ts
โโโ ๐ api/ // ๐ API ํต์
โ โโโ getUser.ts
โ โโโ updateUser.ts
โโโ ๐ lib/ // ๐ง ์ ํธ๋ฆฌํฐ ํจ์
โ โโโ validation.ts
โโโ ๐ index.ts // ๐ค Public API
๊ฐ ์ธ๊ทธ๋จผํธ์ ์ญํ
1. UI ์ธ๊ทธ๋จผํธ ๐จ
์ญํ : ์ฌ์ฉ์์๊ฒ ๋ณด์ด๋ ์ปดํฌ๋ํธ
// features/follow-user/ui/FollowButton.tsx
import { useFollowUser } from '../model/useFollowUser'
import { Button } from '@/shared/ui/button'
export const FollowButton = ({ userId }: { userId: string }) => {
const { isFollowing, toggle, isLoading } = useFollowUser(userId)
return (
<Button
onClick={toggle}
disabled={isLoading}
variant={isFollowing ? 'secondary' : 'primary'}
>
{isFollowing ? 'ํ๋ก์' : 'ํ๋ก์ฐ'}
</Button>
)
}
2. Model ์ธ๊ทธ๋จผํธ ๐
์ญํ : ์ํ ๊ด๋ฆฌ, ๋น์ฆ๋์ค ๋ก์ง, ํ์ ์ ์
// features/follow-user/model/types.ts
export interface FollowState {
followingIds: string[]
isLoading: boolean
error: string | null
}
// features/follow-user/model/useFollowUser.ts
import { useState } from 'react'
import { followUserApi, unfollowUserApi } from '../api'
export const useFollowUser = (userId: string) => {
const [isFollowing, setIsFollowing] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const toggle = async () => {
setIsLoading(true)
try {
if (isFollowing) {
await unfollowUserApi(userId)
} else {
await followUserApi(userId)
}
setIsFollowing(!isFollowing)
} finally {
setIsLoading(false)
}
}
return { isFollowing, toggle, isLoading }
}
3. API ์ธ๊ทธ๋จผํธ ๐
์ญํ : ์๋ฒ์์ ๋ฐ์ดํฐ ํต์
// features/follow-user/api/followUser.ts
import { apiClient } from '@/shared/api/client'
export const followUserApi = (userId: string) => {
return apiClient.post(`/users/${userId}/follow`)
}
export const unfollowUserApi = (userId: string) => {
return apiClient.delete(`/users/${userId}/follow`)
}
4. Lib ์ธ๊ทธ๋จผํธ ๐ง
์ญํ : ํด๋น ์ฌ๋ผ์ด์ค์์๋ง ์ฌ์ฉํ๋ ์ ํธ๋ฆฌํฐ
// features/edit-profile/lib/validation.ts
export const validateUsername = (username: string): boolean => {
return username.length >= 3 && username.length <= 20
}
export const validateEmail = (email: string): boolean => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
๐ Public API: ์ธ๋ถ ์ธํฐํ์ด์ค ๊ด๋ฆฌ
Public API๋?
๊ฐ ์ฌ๋ผ์ด์ค๋ index.ts ํ์ผ์ ํตํด ์ธ๋ถ์ ๋ ธ์ถํ ๊ฒ๋ง ์ ๋ณ์ ์ผ๋ก ๊ณต๊ฐํฉ๋๋ค.
์ฅ์
- ์บก์ํ ๐ฆ
- ๋ด๋ถ ๊ตฌํ์ ์จ๊ธฐ๊ณ ์ธํฐํ์ด์ค๋ง ๋ ธ์ถ
- ๋ฆฌํฉํ ๋ง ์์ ์ฑ ๐
- ๋ด๋ถ ๊ตฌ์กฐ ๋ณ๊ฒฝ ์ ์ธ๋ถ ์ํฅ ์ต์ํ
- ๋ช
ํํ ๊ณ์ฝ ๐
- ๋ฌด์์ ์ฌ์ฉํ ์ ์๋์ง ๋ช ํ
์์
// features/follow-user/index.ts
// โ
์ธ๋ถ์ ๊ณต๊ฐํ ๊ฒ๋ง export
export { FollowButton } from './ui/FollowButton'
export { useFollowUser } from './model/useFollowUser'
export type { FollowState } from './model/types'
// โ ๋ด๋ถ ๊ตฌํ์ ๊ณต๊ฐํ์ง ์์
// export { followUserApi } from './api/followUser'
// ๋ค๋ฅธ ๋ ์ด์ด์์ ์ฌ์ฉ
import { FollowButton, useFollowUser } from '@/features/follow-user'
// โ ๊ธ์ง: ๋ด๋ถ ํ์ผ ์ง์ ์ ๊ทผ
// import { FollowButton } from '@/features/follow-user/ui/FollowButton'
Feature-Sliced Design(FSD)๋ ๋๊ท๋ชจ ํ๋ก ํธ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ง๋ณด์์ฑ๊ณผ ํ์ฅ์ฑ์ ๋์ด๊ธฐ ์ํด ๊ณ ์๋ ์ํคํ ์ฒ ๋ฐฉ๋ฒ๋ก ์ด๋ค. FSD์ ๊ธฐ๋ณธ ๊ฐ๋ ๊ณผ ์์น, ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ ์์ ์ด์ ์ ์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์ฐธ๊ณ ๋ฌธํ
https://velog.io/@clydehan/FSDFeature-Sliced-Design-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C
'๐บ React & Next.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ๋ชจ๋ ธ๋ ํฌ vs ๋ฉํฐ๋ ํฌ, ์ํคํ ์ฒ ๊ฐ์ด๋ (0) | 2025.10.18 |
|---|---|
| TurboRepo์ PNPM (0) | 2025.10.16 |
| "๋ฐฐํฌ๋ ํ๋ฒ๋ง" Vercel CI/CD๋ก ์นผํด๋ฅผ ์๋น๊ธฐ๋ ๋ฒ (0) | 2025.09.23 |
| Recoil atom๋ง ์จ๋ดค๋ค๋ฉด ์ฃผ๋ชฉ! isLoading, isError์ ์๋ณํ๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ ์ค์ ํจํด ๐ (0) | 2025.09.22 |
| Recoil: React ์ํ ๊ด๋ฆฌ์ ์๋ก์ด ํจ๋ฌ๋ค์ (0) | 2025.09.05 |