- harness: 도메인 한 줄 → 에이전트 팀 자동 생성 메타 스킬 (v1.2.0) - zio-harness: React + Spring Boot + Mobile 풀스택 개발 에이전트 팀 (v1.0.0) - 에이전트 4종: orchestrator / analyst / bot / agent - PROJECT_MAP.md 폴더 구조 메모리 시스템 - references/: react, spring-boot, mobile, playwright, database, folder-map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
161 lines
3.6 KiB
Markdown
161 lines
3.6 KiB
Markdown
# 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 <Skeleton />;
|
|
|
|
return (
|
|
<div onClick={() => onSelect?.(userId)}>
|
|
{user.name}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### API 훅 패턴 (TanStack Query)
|
|
```typescript
|
|
// src/api/users.ts
|
|
export function useUser(id: string) {
|
|
return useQuery({
|
|
queryKey: ['user', id],
|
|
queryFn: () => api.get<User>(`/api/v1/users/${id}`),
|
|
});
|
|
}
|
|
|
|
export function useCreateUser() {
|
|
return useMutation({
|
|
mutationFn: (data: CreateUserRequest) =>
|
|
api.post<User>('/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<AuthState>((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: <Layout />, children: [
|
|
{ index: true, element: <Home /> },
|
|
{ path: 'users', element: <UserList /> },
|
|
{ path: 'users/:id', element: <UserDetail /> },
|
|
]},
|
|
{ path: '/login', element: <Login /> },
|
|
]);
|
|
```
|
|
|
|
---
|
|
|
|
## 환경 변수 패턴
|
|
|
|
```
|
|
# .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` |
|