# 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` |