프로젝트가 커질수록 아키텍처의 중요성이 드러납니다. 처음부터 좋은 구조를 잡으면 유지보수가 쉬워집니다.


Feature-Based 구조 (권장)

PLAINTEXT
src/
├── app/                    # 앱 진입점, Provider, Navigation
│   ├── App.tsx
│   ├── navigation/
│   │   ├── RootNavigator.tsx
│   │   └── AuthNavigator.tsx
│   └── providers/
│       └── AppProviders.tsx
├── features/               # 기능별 모듈
│   ├── auth/
│   │   ├── screens/
│   │   │   ├── LoginScreen.tsx
│   │   │   └── RegisterScreen.tsx
│   │   ├── components/
│   │   │   └── LoginForm.tsx
│   │   ├── hooks/
│   │   │   └── useAuth.ts
│   │   ├── api/
│   │   │   └── authApi.ts
│   │   ├── store/
│   │   │   └── authStore.ts
│   │   └── types.ts
│   ├── home/
│   │   ├── screens/
│   │   ├── components/
│   │   └── hooks/
│   └── profile/
├── shared/                 # 공유 모듈
│   ├── components/         # 공용 UI 컴포넌트
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   └── Card.tsx
│   ├── hooks/              # 공용 Hook
│   │   ├── useDebounce.ts
│   │   └── useAsync.ts
│   ├── utils/              # 유틸리티
│   │   ├── format.ts
│   │   └── validation.ts
│   ├── constants/          # 상수
│   │   └── config.ts
│   └── types/              # 공용 타입
│       └── common.ts
├── theme/                  # 테마 시스템
│   ├── colors.ts
│   ├── typography.ts
│   └── spacing.ts
├── i18n/                   # 다국어
├── api/                    # API 클라이언트
│   └── client.ts
└── store/                  # 전역 스토어
    └── index.ts

레이어 분리

PLAINTEXT
┌──────────────────────────────────┐
│         Presentation Layer       │  ← Screen, Component
│      (UI, 사용자 인터랙션)        │
├──────────────────────────────────┤
│          Application Layer       │  ← Hook, Store
│    (비즈니스 로직, 상태 관리)      │
├──────────────────────────────────┤
│            Data Layer            │  ← API, Repository
│    (데이터 소스, API 통신)        │
└──────────────────────────────────┘

의존성 방향: Presentation → Application → Data
Data Layer가 변해도 Presentation은 영향 없음

실전 예제

TSX
// Data Layer — API 호출
// features/auth/api/authApi.ts
export const authApi = {
  login: (email: string, password: string) =>
    apiClient.post<LoginResponse>('/auth/login', { email, password }).then(r => r.data),
  getProfile: () =>
    apiClient.get<User>('/auth/me').then(r => r.data),
};

// Application Layer — 비즈니스 로직
// features/auth/hooks/useLogin.ts
export function useLogin() {
  const mutation = useMutation({
    mutationFn: ({ email, password }: LoginForm) => authApi.login(email, password),
    onSuccess: (data) => {
      useAuthStore.getState().setToken(data.token);
    },
  });
  return mutation;
}

// Presentation Layer — UI
// features/auth/screens/LoginScreen.tsx
export function LoginScreen() {
  const { mutate: login, isPending } = useLogin();
  // UI만 담당
  return <LoginForm onSubmit={login} isLoading={isPending} />;
}

Import 규칙

TSX
// 절대 경로 설정 (tsconfig.json)
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@features/*": ["src/features/*"],
      "@shared/*": ["src/shared/*"],
      "@theme/*": ["src/theme/*"]
    }
  }
}

// 사용
import { Button } from '@shared/components/Button';
import { useAuth } from '@features/auth/hooks/useAuth';
import { colors } from '@theme/colors';

모듈 경계 규칙

  1. feature 간 직접 import 금지 — 공유 필요한 것은 shared/로 이동
  2. Screen은 Hook과 Component를 조합 하는 역할만
  3. API 호출은 반드시 api/ 폴더에서만 — 컴포넌트에서 직접 fetch 금지
  4. ** 비즈니스 로직은 Hook에서** — 컴포넌트는 UI에만 집중

정리

  • Feature-Based 구조 로 기능별로 코드를 묶으면 관련 파일을 빠르게 찾을 수 있습니다
  • Presentation, Application, Data 레이어를 분리하면 테스트와 교체가 쉽습니다
  • ** 절대 경로 **를 설정하면 import가 깔끔해집니다
  • feature 간 ** 직접 의존을 피하고** 공유 모듈을 통해 소통하세요
  • 처음부터 완벽한 아키텍처를 만들려 하지 말고, ** 프로젝트 성장에 맞춰 진화 **시키세요
댓글 로딩 중...