# React 개발 가이드 ## 스택 가정 - React 18+ + TypeScript - Vite 또는 Next.js - 상태 관리: Zustand (소규모) / Redux Toolkit (대규모) - 스타일: Tailwind CSS 또는 shadcn/ui - API 클라이언트: Axios + TanStack Query (서버 상태) --- ## 폴더 구조 원칙 ``` src/ ├── pages/ # 라우트 단위 페이지 (Next.js: app/ 또는 pages/) ├── components/ │ ├── ui/ # 재사용 원자 컴포넌트 (Button, Input, Modal) │ └── features/ # 도메인별 조합 컴포넌트 ├── hooks/ # 커스텀 훅 (use 접두사 필수) ├── store/ # Zustand store 또는 Redux slices ├── api/ # API 호출 함수 + TanStack Query hooks ├── types/ # 전역 TypeScript 타입 └── utils/ # 순수 유틸리티 함수 ``` --- ## 컴포넌트 작성 패턴 ### 기본 구조 ```tsx // src/components/features/UserCard.tsx interface UserCardProps { userId: string; onSelect?: (id: string) => void; } export function UserCard({ userId, onSelect }: UserCardProps) { const { data: user } = useUser(userId); // API 훅 if (!user) return ; return (
onSelect?.(userId)}> {user.name}
); } ``` ### API 훅 패턴 (TanStack Query) ```typescript // src/api/users.ts export function useUser(id: string) { return useQuery({ queryKey: ['user', id], queryFn: () => api.get(`/api/v1/users/${id}`), }); } export function useCreateUser() { return useMutation({ mutationFn: (data: CreateUserRequest) => api.post('/api/v1/users', data), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }), }); } ``` ### Zustand Store 패턴 ```typescript // src/store/authStore.ts interface AuthState { user: User | null; token: string | null; setAuth: (user: User, token: string) => void; logout: () => void; } export const useAuthStore = create((set) => ({ user: null, token: null, setAuth: (user, token) => set({ user, token }), logout: () => set({ user: null, token: null }), })); ``` --- ## API 클라이언트 설정 ```typescript // src/api/client.ts const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 10000, }); api.interceptors.request.use((config) => { const token = useAuthStore.getState().token; if (token) config.headers.Authorization = `Bearer ${token}`; return config; }); api.interceptors.response.use( (res) => res.data, (err) => { if (err.response?.status === 401) useAuthStore.getState().logout(); return Promise.reject(err); } ); ``` --- ## 라우팅 구조 (React Router v6) ```typescript // src/pages/Router.tsx const router = createBrowserRouter([ { path: '/', element: , children: [ { index: true, element: }, { path: 'users', element: }, { path: 'users/:id', element: }, ]}, { path: '/login', element: }, ]); ``` --- ## 환경 변수 패턴 ``` # .env.local (git 제외) VITE_API_URL=http://localhost:8080 VITE_APP_NAME=MyApp ``` TypeScript에서 타입 안전하게 접근: ```typescript // src/config.ts export const config = { apiUrl: import.meta.env.VITE_API_URL, appName: import.meta.env.VITE_APP_NAME, } as const; ``` --- ## 컨벤션 | 항목 | 규칙 | 예시 | |------|------|------| | 컴포넌트 파일 | PascalCase.tsx | `UserProfile.tsx` | | 훅 파일 | camelCase.ts, use 접두사 | `useAuth.ts` | | 유틸 함수 | camelCase.ts | `formatDate.ts` | | 상수 | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` | | 인터페이스 | PascalCase, I 접두사 없음 | `User`, `CreateUserRequest` |