harness/skills/zio-harness/references/react.md
ythong c25e55b64b feat: harness + zio-harness Claude Code 플러그인 초기 배포
- 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>
2026-06-17 13:48:56 +09:00

3.6 KiB

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/          # 순수 유틸리티 함수

컴포넌트 작성 패턴

기본 구조

// 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)

// 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 패턴

// 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 클라이언트 설정

// 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)

// 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에서 타입 안전하게 접근:

// 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