FlipFlick-FEλ React + TypeScript κΈ°λ°μΌλ‘ ꡬμ±λ νλ‘ νΈμλ νλ‘μ νΈμ λλ€. Viteλ‘ κ΅¬μ±λ λΉ λ₯Έ κ°λ° νκ²½ μμ μ΅μ λΌμ΄λΈλ¬λ¦¬λ₯Ό λμ νμ¬, μ μ°νκ³ μ μ§λ³΄μκ° μ¬μ΄ ꡬ쑰λ₯Ό μ§ν₯ν©λλ€.
- μ΄ README μ 체λ₯Ό λΉ λ₯΄κ² νμ΄λ³΄μΈμ.
src/App.tsxμμ μ 체 ꡬ쑰 νμΈ -router/index.tsxμμ λΌμ°ν νλ¦ μ΄ν΄pages/-components/-services/μμΌλ‘ κΈ°λ₯ νλ¦ λ°λΌκ°κΈ°- μ μ μ€νμΌ(
styles/)κ³Ό μ μ μν(context/)λ ꡬ쑰 νμ ν μ°Έμ‘° - κ°λ° κ°μ΄λλΌμΈ - μ»΄ν¬λνΈ κ°λ° (κΆμ₯) β νλ β
- React 19: μ΅μ λ²μ μ Reactλ₯Ό μ¬μ©νμ¬ UI ꡬμ±
- TypeScript: νμ μμ μ±μ μ 곡νλ JavaScript μνΌμ
- Vite: λΉ λ₯Έ κ°λ° μλ²μ μ΅μ νλ λΉλλ₯Ό μ 곡νλ νλ‘ νΈμλ λꡬ
- React Router DOM: ν΄λΌμ΄μΈνΈ μ¬μ΄λ λΌμ°ν
- Context API: React λ΄μ₯ μν κ΄λ¦¬ (νμμ λ°λΌ νμ₯ κ°λ₯)
- μν 볡μ‘λκ° μ¦κ°νλ κ²½μ° Zustand λλ Redux λ± λμ μ κ²ν
- Styled Components: CSS-in-JS λΌμ΄λΈλ¬λ¦¬
- CLSX: μ‘°κ±΄λΆ ν΄λμ€ μ΄λ¦ κ΄λ¦¬
- Axios: HTTP ν΄λΌμ΄μΈνΈ
- Dayjs: λ μ§ λ° μκ° μ²λ¦¬
- React-Toastify: μλ¦Ό λ©μμ§ νμ
- Storybook: UI μ»΄ν¬λνΈ κ°λ° λ° λ¬Έμν
- ESLint: μ½λ νμ§ λ° μ€νμΌ κ²μ¬
- Prettier: μ½λ ν¬λ§·ν
- Vitest: ν μ€νΈ νλ μμν¬
- Husky & Commitlint: Git ν λ° μ»€λ° λ©μμ§ κ²μ¦
- @iconify/react: μμ΄μ½ λΌμ΄λΈλ¬λ¦¬
- lucide-react: μμ΄μ½ μ»΄ν¬λνΈ λΌμ΄λΈλ¬λ¦¬
νμ¬ νλ‘μ νΈμμλ μμ΄μ½μ λ€μκ³Ό κ°μ΄ μ¬μ©νκ³ μμ΅λλ€:
- BaseButton.tsxμμλ
@iconify/reactμIconμ»΄ν¬λνΈλ₯Ό μ¬μ© - ExamplePage.tsxμμλ
lucide-reactμμ μ§μ μμ΄μ½ μ»΄ν¬λνΈλ₯Ό importνμ¬ μ¬μ©
src/
βββ App.tsx # μ΅μμ μ»΄ν¬λνΈ, Router λ° GlobalStyle μ£Όμ
βββ main.tsx # μ± μ§μ
μ
βββ assets/ # μ΄λ―Έμ§, ν°νΈ λ± μ μ μμ°
βββ components/ # UI μ»΄ν¬λνΈ (κ³΅ν΅ λ° κΈ°λ₯λ³)
β βββ common/ # κ³΅ν΅ UI (BaseButton, Modal, Layout λ±)
β βββ feature/ # λλ©μΈ/κΈ°λ₯λ³ UI (μ μ μΉ΄λ, ν« λ·°μ΄ λ±)
β βββ (μμ)auth/ # μΈμ¦ κ΄λ ¨ μ»΄ν¬λνΈ (λ‘κ·ΈμΈ, νμκ°μ
λ±)
β βββ (μμ)mypage/ # λ§μ΄νμ΄μ§ κ΄λ ¨ μ»΄ν¬λνΈ
βββ constants.ts # μμ μ μ (URL, λ©μμ§, enum λ±)
βββ context/ # κΈλ‘λ² μν κ΄λ¦¬ (ThemeContext, AuthContext λ±)
βββ hooks/ # 컀μ€ν
React Hook
βββ pages/ # λΌμ°ν
λμ νμ΄μ§ μ»΄ν¬λνΈ (HomePage, MyPage, LoginPage λ±)
βββ router/ # μ 체 λΌμ°ν
μ μ
β βββ index.tsx # λΌμ°ν
μ€μ
βββ services/ # API νΈμΆ μ μ (Axios κΈ°λ°)
βββ stories/ # Storybook μ€ν 리
βββ styles/ # reset, globalStyle, theme λ± μ μ μ€νμΌ
βββ types/ # κ³΅ν΅ νμ
, API μλ΅ νμ
λ±(κΆμ₯, νμ
μ μΈ μμΉλ κ°μΈ μ ν )
βββ api.ts # API μλ΅ νμ
μ μ
> κ° λλ ν 리μ μ±
μκ³Ό νμΌ μΆκ° κΈ°μ€μ νλ¨ "λλ ν λ¦¬λ³ μμ± κΈ°μ€" μ°Έμ‘°
- Node.js 18.x μ΄μ
- npm λλ yarn
# 1. μ μ₯μ ν΄λ‘
git clone https://github.com/your-username/flipflick-fe.git
cd flipflick-fe
# 2. μμ‘΄μ± μ€μΉ
npm install
# λλ
yarn install
# 3. κ°λ° μλ² μ€ν
npm run dev
- κ°λ° μλ²: http://localhost:5173
# κ°λ° μλ² μ€ν (http://localhost:5173)
npm run dev
# νλ‘λμ
λΉλ
npm run build
# λΉλ κ²°κ³Όλ¬Ό 미리보기
npm run preview
# λ¦°νΈ κ²μ¬
npm run lint
# λ¦°νΈ μ€λ₯ μλ μμ
npm run lint:fix
# μ½λ ν¬λ§·ν
npm run format
# ν¬λ§·ν
κ²μ¬
npm run format:check
# Storybook μ€ν (http://localhost:6006)
npm run storybook
# Storybook λΉλ
npm run build-storybook
# ν
μ€νΈ μ€ν
npm run test- μ΄μλ₯Ό μμ±νμ¬ κΈ°λ₯ μμ² λλ λ²κ·Έ μμ μ μ μν©λλ€.
- μμ±ν μ΄μ μ’ λ₯μ μ΄μ λ²νΈλ‘ μ λΈλμΉλ₯Ό μμ±ν©λλ€:
- λ³κ²½μ¬νμ 컀λ°ν©λλ€: Conventional Commitsμ μ¬μ©νκ±°λ ν΄λΉ κ·μΉμ λ°λ¦ λλ€.
- λΈλμΉλ₯Ό νΈμν©λλ€:
git push origin feature/amazing-feature - GitHubμμ Pull Requestλ₯Ό μμ±ν©λλ€.
- μ λͺ©μ
[FEAT] κΈ°λ₯ μ€λͺλλ[FIX] κΈ°λ₯ μ€λͺνμμ λ°λ¦ λλ€. - PR μ€λͺ μ λ³κ²½μ¬νκ³Ό κ΄λ ¨λ μ΄μ λ²νΈλ₯Ό λͺ μν©λλ€.
- μ:
μ΄ PRμ #00 μ΄μλ₯Ό ν΄κ²°ν©λλ€. - PR ν νλ¦Ώμ μ¬μ©νμ¬ νμν μ 보λ₯Ό μμ±ν©λλ€.
- PR μ μΆμλ₯Ό λ³ΈμΈμΌλ‘ μ§μ νκ³ λ¦¬λ·°μ΄λ₯Ό μ§μ ν©λλ€.
- PRμ΄ μΉμΈλλ©΄
mainλΈλμΉμ λ³ν©ν©λλ€.
| λλ ν 리 | μ± μ | νμΌ μΆκ° κΈ°μ€ |
|---|---|---|
components/common/ |
λ²μ© UI | λ²νΌ, λͺ¨λ¬ λ± μ¬μ¬μ© κ°λ₯ UI |
components/feature/ |
κΈ°λ₯ μ€μ¬ UI | νΉμ κΈ°λ₯ μ μ© UI (ex. μ μ μΉ΄λ) |
constants.ts |
μ μ μμ | URL, λ©μμ§, enum, μ κ·μ |
context/ |
μ μ μν | Auth, Modal λ± App λ¨μ μν |
hooks/ |
컀μ€ν ν | μ¬μ¬μ© κ°λ₯ν λ‘μ§ useModal.ts, useFetch.ts λ± |
pages/ |
λΌμ°νΈ λ¨μ νμ΄μ§ | HomePage.tsx, LoginPage.tsx λ± |
router/ |
λΌμ°ν μ€μ | Route, Layout, ProtectedRoute λ± |
services/ |
API μμ² | userService.ts, problemService.ts λ± |
styles/ |
κΈλ‘λ² μ€νμΌ | reset, theme, κΈλ‘λ² CSS |
types/api.ts |
νμ μ μ | API μλ΅ νμ , μ μ μ νΈ νμ |
| λΆλ₯ | μμΉ | μ€λͺ |
|---|---|---|
| μ μ νμ | types/api.ts |
λͺ¨λ API μλ΅ λ° κ³΅ν΅ μ νΈ νμ |
| Props νμ | μ»΄ν¬λνΈ νμΌ λ΄λΆ | κ°λ¨ν propsλ inline λλ μ»΄ν¬λνΈ λ΄λΆ μ μΈ |
| μ νΈ νμ | types/api.ts λ΄ μ μ |
Pagination, ApiResponse<T> λ± |
- any μ¬μ© κΈμ§
- λ°ν νμ λͺ μ νμ
- readonly, as const, Omit, Pick μ κ·Ή μ¬μ©
- λ°λ³΅ ꡬ쑰λ μ λ€λ¦ μ νΈ νμ μΌλ‘ λΆλ¦¬
// services/userService.ts
import AxiosInstance from './axios'
export const getUserInfo = () => {
return AxiosInstance.get('/user/info')
}- λͺ¨λ μν¬νΈλ λͺ μμ μΌλ‘ μμ±ν©λλ€. (μλ μν¬νΈ μ¬μ© κΈμ§)
- κ³΅ν΅ μ»΄ν¬λνΈλ
components/commonλλ ν 리μ λ°°μΉν©λλ€. - νΉμ κΈ°λ₯μ κ΄λ ¨λ μ»΄ν¬λνΈλ
components/featureλλ ν 리μ λ°°μΉν©λλ€. - μν κ΄λ¦¬, api νΈμΆ, νμ μ μΈ, 컀μ€ν ν λ±μ ν΄λΉ μ»΄ν¬λνΈμ κ°κΉμ΄ μμΉμ λ°°μΉν©λλ€.
- μ€νμΌμ μ»΄ν¬λνΈ λ΄λΆμμ
styled-componentsλ₯Ό μ¬μ©νμ¬ μμ±ν©λλ€. - λ€λ₯Έ μ½λμ μν₯μ΄ μλ€λ©΄ μ± μ λ° μμΉ λΆλ¦¬λ₯Ό μλ°νκ² μ μ©νμ§ μμλ λ©λλ€.(μ μ¬νλ€μ κΆμ₯ μ¬νμ λλ€)
- (μ ν) μ»΄ν¬λνΈ κ°λ° μ Storybookλ₯Ό ν΅ν΄ μ»΄ν¬λνΈμ λν΄ λ¬Έμνν μ μμ΅λλ€.
- Conventional Commits νμμ λ°λ¦
λλ€:
feat:μλ‘μ΄ κΈ°λ₯fix:λ²κ·Έ μμ docs:λ¬Έμ λ³κ²½style:μ½λ μ€νμΌ λ³κ²½ (κΈ°λ₯ λ³κ²½ μμ)refactor:리ν©ν λ§ (κΈ°λ₯ λ³κ²½ μμ)test:ν μ€νΈ μΆκ° λλ μμ chore:λΉλ νλ‘μΈμ€ λλ λꡬ λ³κ²½
Vitestλ₯Ό μ¬μ©νμ¬ λ¨μ ν
μ€νΈ λ° ν΅ν© ν
μ€νΈλ₯Ό μμ±ν©λλ€. ν
μ€νΈ νμΌμ κ΄λ ¨ μ»΄ν¬λνΈλ κΈ°λ₯ κ·Όμ²μ .test.ts λλ .test.tsx νμ₯μλ‘ λ°°μΉν©λλ€.
# λͺ¨λ ν
μ€νΈ μ€ν
npm run test
# νΉμ ν
μ€νΈ νμΌ μ€ν
npm run test -- path/to/test/file.test.tsνλ‘μ νΈ λ°°ν¬ λ°©λ²μ λν μ§μΉ¨ μΆκ° νμ.
# νλ‘λμ
λΉλ μμ±
npm run build
# λΉλ κ²°κ³Όλ¬Όμ dist/ λλ ν 리μ μμ±λ©λλ€νμ¬ λλ ν 리 ꡬ쑰λ λλ©μΈ μ€μ¬ + μν λ―Ή κ΅¬μ‘°κ° νΌν©λ React + TypeScript + Vite νλ‘μ νΈμ λλ€. κ° λλ ν 리μ μ©λμ λ°λΌ κΈ°λ₯ μΆκ°, 리ν©ν λ§, λ²κ·Έ μμ μ μ νμΌμ μμΉλ λ€μ κΈ°μ€μ λ°λ¦ λλ€:
| λλ ν 리 | μ©λ | μΈμ /무μμ μΆκ°νλ? |
|---|---|---|
src/App.tsx, src/main.tsx |
μ± μ§μ μ | κΈλ‘λ² λ μ΄μμ, μ΅μμ λΌμ°ν , Provider μ£Όμ λ± μ μ μ€μ μ μμ |
src/assets |
μ μ μμ° (μ΄λ―Έμ§ λ±) | μμ΄μ½, μ΄λ―Έμ§, ν°νΈ, λ°°κ²½ λ± μΆκ°ν λ μ¬μ© - react.svgλ μμ - styles/λ CSS, κΈλ‘λ² μ€νμΌ λλ ν°νΈ λ± κ°λ₯ |
src/components/common |
μ¬μ¬μ© κ°λ₯ν UI μ»΄ν¬λνΈ | λ²νΌ, λͺ¨λ¬, ν μ€νΈ, ν λ± λ²μ©μ UI μΆκ° |
components/common/layout |
κ³΅μ© λ μ΄μμ μ»΄ν¬λνΈ | BasePageLayout, AuthLayout λ± νμ΄μ§ ꡬ쑰λ₯Ό λ΄λΉνλ κ³΅μ© UI |
| src/components/feature | νΉμ λλ©μΈ μ’
μ UI μ»΄ν¬λνΈ | μ μ μΉ΄λ, ν« λ·°μ΄, κ²μν λ± νΉμ κΈ°λ₯ μ μ© μ»΄ν¬λνΈ μΆκ° |
| src/constants | μμ μ μ | μ μ enum, URL, λ©μμ§, μ κ·μ, λ§λ² μ«μ/λ¬Έμ μ κ±°μ© |
| src/context | μ μ μν κ΄λ¦¬ (React Context) | μλ‘μ΄ μ μ μν μΆκ° μ: xxxContext.tsx μμ±
ex)
AuthContext, ThemeContext λ± |
| src/hooks | μ μ μ¬μ¬μ© 컀μ€ν
ν
| useModal.ts, useUserInfo.ts, useFetch.ts λ± μ¬μ¬μ© κ°λ₯ν λ‘μ§ μμ± |
| src/pages | λΌμ°νΈ λ¨μ νμ΄μ§ μ»΄ν¬λνΈ | μλ‘μ΄ λΌμ°νΈ μμ± μ μ΄ λλ ν 리μ Page λ¨μ μ»΄ν¬λνΈ μμ±
ex) HomePage.tsx,
MyPage.tsx λ± |
| src/router | λΌμ°ν
μ€μ | νμ΄μ§ μΆκ°/λ³κ²½ μ λΌμ°νΈλ μ¬κΈ°μ μ μ λλ κ°±μ |
| src/services | μΈλΆ API νΈμΆ (Axios λ±) | api/user.ts, api/problem.ts λ± REST API μ°κ²° μλΉμ€
μμ± |
| src/stories | Storybook κ΄λ ¨ | κ° μ»΄ν¬λνΈ μ€ν 리 λ° λ°λͺ¨μ© μμ νμΌ μμ± |
| src/styles | κΈλ‘λ² CSS/Styled-component theme λ± | ν
λ§, κ³΅ν΅ μ€νμΌ, reset.css λ± μμ± |
| src/types | μ μ νμ
μ μ | μ μ κ³΅ν΅ νμ
, μ νΈ νμ
, API μλ΅ νμ
λ±μ μ μ |
| λͺ©μ | μΆκ° μμΉ |
|---|---|
| μλ‘μ΄ νμ΄μ§ μμ± | src/pages/SomePage.tsx |
| νμ΄μ§ λΌμ°ν μΆκ° | src/router/index.ts |
| μ μ μν κ΄λ¦¬ μΆκ° | src/context/SomeContext.tsx |
| κ³΅ν΅ λ²νΌ/μ λ ₯μ°½ μ»΄ν¬λνΈ μΆκ° | src/components/common/MyButton.tsx |
| λλ©μΈ νΉν UI μ»΄ν¬λνΈ μΆκ° | src/components/feature/ λ΄μ UserCard.tsx, PetViewer.tsx λ± |
| REST API μ°κ²° | src/services/xxxService.ts |
| 컀μ€ν ν μμ± | src/hooks/useXxx.ts |
| μ μ μμ/enum | src/constants/xxx.ts |
| κΈλ‘λ² μ€νμΌ | src/styles/globalStyle.ts |
| μ΄λ―Έμ§, μμ΄μ½ | src/assets/ λλ src/assets/icons/ λ±μ μ¬μ© λλ©μΈ μμΉμ ν΄λ ꡬ쑰 μμ± |
| λΌμ°νΈ μ μ | src/router/index.tsxμ <Routes> λ΄λΆμ <Route> μ μ |
| λΌμ°ν° μ μ© | src/App.tsx <BrowserRouter>λ‘ κ°μΈκΈ° |
| νμ΄μ§ μ»΄ν¬λνΈ | src/pages/ κ°κ°μ URLμ λμλλ UI μ»΄ν¬λνΈ |
- μ¬μ¬μ© κ°λ₯νλ©΄
commonλλhooks, λλ©μΈ νΉνλ©΄featureλ‘ - (ui, hooks, λ±)
- API λ‘μ§μ
servicesμ μ μν λ‘μ§μcontext, UI λ‘μ§μcomponentsλ‘ λΆλ¦¬ - νμ΄μ§ λ¨μ μ»΄ν¬λνΈλ
pages, ui μ»΄ν¬λνΈλcomponentsλ‘ λΆλ¦¬ - νΉμ,
components/feature/{κΈ°λ₯/λλ©μΈ μ΄λ¦}/,page/{κΈ°λ₯/λλ©μΈ μ΄λ¦}μ μμ±
- λΌμ°ν
UIλ©΄
pages, 쑰립 UI면components
.svg,.pngλ±μassets/, μ»΄ν¬λνΈλcomponents/- μ μ μμλ
constants.tsμ μ μ - μ μ μ€νμΌμ
styles/μ μμ± - μ μ νμ
μ
types/μ μμ± - API μλ΅ νμ
μ
types/api.tsμ μμ± - 컀μ€ν
ν
μ
hooks/μ μμ± - μλΉμ€ API νΈμΆμ
services/μ μμ± - λΌμ°ν
μ€μ μ
router/μ μμ± - μ μ μν κ΄λ¦¬λ
context/μ μμ±
- React Router v6 μ΄μμμ κΆμ₯λλ λ μ΄μμ λ° νμ΄μ§ κ΅¬μ± λ°©μμΌλ‘ κ³΅μ© λ μ΄μμκ³Ό κ°λ³ νμ΄μ§ λ° κ°λ³ λ μ΄μμμ λΆλ¦¬νμ¬ κ΄λ¦¬ν©λλ€.
<Routes>
{/* κ³΅μ© λ μ΄μμ μ μ© κ·Έλ£Ή */}
<Route element={<BasePageLayout/>}>
<Route path="/" element={<HomePage/>}/>
<Route path="/mypage" element={<MyPage/>}/>
<Route path="/about" element={<AboutPage/>}/>
</Route>
{/* κ³΅μ© λ μ΄μμμ μ¬μ©νμ§ μλ κ°λ³ νμ΄μ§ λ° λ³λ λ μ΄μμ μ‘΄μ¬ μ*/}
<Route element={<AuthLayout/>}>
<Route path="/login" element={<LoginPage/>}/>
<Route path="/signup" element={<SignupPage/>}/>
</Route>
<Route path="*" element={<NotFoundPage/>}/>
</Routes>- μ»΄ν¬λνΈ λ¨μλ Storybookμ λ§λ€λ©΄
src/storiesμ μμ±νμ¬ μ»΄ν¬λνΈ λ¨μλ‘ λλλ§ λ° νμΈν μ μμ- (μλ λ¬Έμν λ° ν μ€νΈ μ©μ΄)