리액트 스터디 5주차 - 프로젝트 구조

Nomore·2025년 9월 29일
0

ReactStudy

목록 보기
6/7

리액트 폴더 구조 — 입문 → 확장 가이드 (처음엔 단순하게, 커지면 질서 있게)

리액트를 처음 접하는 분들께 “폴더/파일을 어떻게 나눌지”를 설명할 때, 용어보다 왜 그렇게 나누는가를 먼저 잡아주면 이해가 빠릅니다. 본 글은 아주 단순한 출발점에서 시작해, 프로젝트가 커질 때 흔들리지 않도록 점진 확장 전략까지 한 번에 정리합니다.


1) 입문기: 작업실 한 칸으로 시작하기

처음에는 src작은 작업실이라 생각하고, 그 안에 세 가지 상자만 둡니다.

  • App : 전체 화면의 뼈대(레이아웃/라우팅의 시작점)
  • components : 재사용 가능한 UI 블록
  • pages : 라우터로 진입하는 “완성된 화면”
my-app/
  index.html
  package.json
  src/
    main.tsx            // 엔트리
    App.tsx             // 앱의 뼈대
    pages/
      HomePage.tsx
    components/
      Greeting.tsx

핵심은 폴더가 역할을 말한다는 감각입니다.
처음에는 스타일도 컴포넌트 파일 옆에 두어 “모양+동작”을 한눈에 보게 하십시오.

왜 이렇게 단순하게 출발할까?
초반에는 “빨리 그려보고 피드백 받는 경험”이 중요합니다. 구조가 복잡하면 속도가 느려집니다.


2) 성장기: 서랍을 하나씩 추가하기(관심사별 분리)

컴포넌트가 늘고 API 호출이 생기면 서랍(폴더)을 기능별로 하나씩 추가합니다. 이때 기준은 단순합니다.

1) 같은 성격끼리 모은다 (hooks, services, utils, types…)
2) 재사용 범위로 깊이를 나눈다 (components/common vs components/home)

components/homeHomePage.tsx 에서만 사용되는 컴포넌트를 모아두는곳

src/
  main.tsx
  App.tsx
  pages/
    HomePage.tsx
    AboutPage.tsx
  components/
    common/
      Button.tsx
      Card.tsx
    home/
      Hero.tsx
      FeatureList.tsx
  hooks/
    useToggle.ts
    useFetch.ts
  services/
    api/
      http.ts           // axios/fetch 래퍼
      users.ts          // /users API
      articles.ts       // /articles API
  styles/
    global.css          // reset/전역 스타일
  assets/
    logo.svg
  utils/
    formatDate.ts
    number.ts
  types/
    article.ts
    user.ts

이 단계의 목적은 “파일이 늘어도 찾기 쉬운 위치가 유지되도록” 만드는 것입니다.


3) 성숙기: 기능(도메인)별 방으로 쪼개기(Feature-first)

규모가 커지면 기능(도메인) 단위로 방을 나누는 편이 유지보수에 유리합니다. 각 방은 그 기능에 필요한 것을 스스로 갖습니다(컴포넌트, 훅, API, 타입 등).

src/
  app/
    routes.tsx          // 라우팅 정의
    providers.tsx       // 전역 Provider(QueryClient, Theme 등)
    store/              // 전역 상태(redux/zustand 등)
  shared/
    components/         // 진짜 범용: 버튼, 다이얼로그
    hooks/
    utils/
    types/
  features/
    auth/
      components/
        LoginForm.tsx
      api/
        auth.api.ts
      hooks/
        useLogin.ts
      types/
        auth.types.ts
      index.ts          // 외부에 공개할 것만 배럴(export)
    articles/
      components/
        ArticleList.tsx
        ArticleItem.tsx
      api/
        articles.api.ts
      hooks/
        useArticles.ts
      types/
        article.types.ts
      index.ts
  pages/
    HomePage.tsx
    ArticlesPage.tsx

설명 포인트는 간단합니다.

  • features는 방입니다. 그 방에서 쓰는 물건(컴포넌트/훅/API/타입)을 그 방 안에 둡니다.
  • shared에는 집안 어디서나 쓸 수 있는 가구(버튼, 모달 등)를 둡니다.
  • app은 집의 배치도(라우팅)와 전역 관리소(프로바이더/스토어)입니다.

이전 단계의 components/home처럼 “특정 페이지 전용 컴포넌트”는 커질 때 해당 도메인 방(예: features/articles)으로 점진적 이동하면 됩니다. 전면 개편 없이도 리팩터링이 가능합니다.


4) 초보자가 자주 헷갈리는 지점, 이렇게 설명하십시오

① 페이지 vs 컴포넌트

  • 페이지는 라우터가 들어오는 입구이고,
  • 컴포넌트는 입구 안에서 조립되는 건축 자재입니다.

② shared vs features

  • shared는 “집안 어디서나 쓰는 가구”,
  • features는 “특정 방에서만 쓰는 물건”입니다.

③ Barrel 파일(index.ts)

  • 기능 폴더의 문 앞 안내문입니다. 외부에서 features/articles만 임포트해도 필요한 공개 항목을 한 번에 가져올 수 있습니다.

④ 경로 별칭(예: @/shared, @/features)

  • ../../../ 지옥을 피하기 위해 tsconfig.json/번들러에 절대경로 별칭을 둡니다.
  • “지도에 좌표를 찍듯” 최상위 기준으로 불러온다고 이해시키면 빠릅니다.

5) 스타일링: 초기엔 가깝게, 커지면 토큰/테마만 공유

  • 초기에는 컴포넌트 파일 옆(또는 내부)에 스타일을 두어 탐색 비용을 낮추십시오(예: styled-components, CSS Modules).
  • 확장 시에는 색상/간격/타입스케일 같은 디자인 토큰과 전역 테마만 shared/styles로 올려 일관성을 확보하고, 개별 컴포넌트는 여전히 co-location을 유지합니다.

간단 예시(초기: 파일 내부에 스타일):

// src/components/common/Button.tsx
import styled from 'styled-components';

const Root = styled.button`
  padding: 8px 12px;
  border-radius: 8px;
  font-weight: 600;
`;

type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: 'primary' | 'ghost';
};

export default function Button({ variant = 'primary', ...rest }: Props) {
  return <Root className={`btn ${variant}`} {...rest} />;
}

확장 예시(도메인 방으로 이동 + 공용 토큰 사용):

// src/features/articles/components/ArticleItem.tsx
import styled from 'styled-components';
import { tokens } from '@/shared/styles/tokens';

const Box = styled.div`
  padding: ${tokens.spacing.md};
  border: 1px solid ${tokens.color.border};
  border-radius: ${tokens.radius.lg};
`;

export function ArticleItem({ title, excerpt }: { title: string; excerpt: string }) {
  return (
    <Box>
      <h3>{title}</h3>
      <p>{excerpt}</p>
    </Box>
  );
}

6) “언제 다음 단계로 넘어갈까?”를 가르는 신호

  • components/에 비슷한 이름이 20개 이상 쌓여 서로 찾기 어려워졌다면 → features로 분리
  • 한 파일이 300~400줄을 넘고 스크롤 탐색이 불편하다면 → 하위 컴포넌트/훅 추출
  • utils/가 잡동사니 창고가 되었다면 → 도메인별 utils로 쪼개거나 shared/ 내부로 재배치
  • API 파일이 1개에서 3개+로 늘며 리소스가 다양해졌다면 → 리소스별 분리(users, articles…)

이 신호를 느낄 때마다 전면 개편 대신 오늘 다루는 코드 범위부터 작게 옮기는 전략이 안전합니다.


마무리: 한 문장으로 요약

  • 처음에는 역할(페이지/컴포넌트)만 구분하는 “작업실 한 칸”으로 시작하고,
  • 파일이 늘어 불편 신호가 생기면 관심사별 서랍을 추가하며,
  • 규모가 커지면 기능(도메인) 단위의 방으로 쪼개 “그 방의 물건은 그 방에” 모은다.

이 흐름을 체득하면, 초심자도 “왜 → 언제 → 어떻게”의 축을 잃지 않고 구조를 점진적으로 확장할 수 있습니다.

0개의 댓글