[React] 확장 가능한 React 프로젝트 구조 설계하기
React 프로젝트를 시작할 때 가장 중요한 결정 중 하나는 프로젝트 구조를 어떻게 설계할 것인가입니다. 잘 설계된 프로젝트 구조는 코드의 가독성을 높이고, 유지보수를 용이하게 하며, 팀 협업을 원활하게 만듭니다. 이 블로그에서는 확장 가능한 React 프로젝트 구조를 설계하는 방법에 대해 자세히 알아보겠습니다.
1. 기본 디렉토리 구조
먼저, 기본적인 디렉토리 구조부터 살펴보겠습니다.
my-react-project/
├── public/
├── src/
│ ├── components/
│ ├── pages/
│ ├── hooks/
│ ├── contexts/
│ ├── services/
│ ├── utils/
│ ├── styles/
│ ├── assets/
│ ├── App.js
│ └── index.js
├── package.json
└── README.md
각 디렉토리의 역할을 살펴보겠습니다:
public/
: 정적 파일들을 저장합니다. (예: index.html, favicon.ico)src/
: 소스 코드가 위치하는 메인 디렉터리입니다.components/
: 재사용 가능한 React 컴포넌트들을 저장합니다.pages/
: 라우트에 해당하는 페이지 컴포넌트들을 저장합니다.hooks/
: 커스텀 React 훅들을 저장합니다.contexts/
: React Context API를 사용한 상태 관리 파일들을 저장합니다.services/
: API 호출 등 외부 서비스와의 통신을 담당하는 파일들을 저장합니다.utils/
: 유틸리티 함수들을 저장합니다.styles/
: 전역 스타일 파일들을 저장합니다.assets/
: 이미지, 폰트 등의 자산 파일들을 저장합니다.
이러한 기본 구조는 프로젝트의 규모가 작을 때는 충분할 수 있지만, 프로젝트가 커지면 더 세분화된 구조가 필요합니다.
2. 컴포넌트 구조화
컴포넌트는 React 애플리케이션의 핵심입니다. 컴포넌트를 효과적으로 구조화하는 것이 중요합니다.
2.1 아토믹 디자인 적용
아토믹 디자인 원칙을 적용하여 컴포넌트를 구조화할 수 있습니다:
src/
└── components/
├── atoms/
│ ├── Button/
│ └── Input/
├── molecules/
│ └── SearchBar/
├── organisms/
│ └── Header/
└── templates/
└── HomeTemplate/
atoms/
: 버튼, 입력 필드 등 가장 기본적인 UI 요소들molecules/
: atoms를 조합한 좀 더 복잡한 UI 요소들organisms/
: molecules와 atoms를 조합한 더 큰 단위의 UI 섹션templates/
: 전체 페이지의 레이아웃을 정의
이렇게 구조화하면 컴포넌트의 재사용성과 일관성을 높일 수 있습니다.
2.2 컴포넌트 파일 구조
각 컴포넌트는 다음과 같은 구조를 가질 수 있습니다:
Button/
├── Button.js
├── Button.test.js
├── Button.module.css
└── index.js
Button.js
: 컴포넌트의 주 로직Button.test.js
: 컴포넌트 테스트 파일Button.module.css
: 컴포넌트 스타일 (CSS 모듈 사용)index.js
: 컴포넌트를 외부로 내보내는 파일
이렇게 구조화하면 각 컴포넌트와 관련된 모든 파일들을 한 곳에서 관리할 수 있습니다.
3. 상태 관리
프로젝트의 규모가 커지면 효과적인 상태 관리가 필수적입니다.
3.1 Context API 사용
간단한 상태 관리는 React의 Context API를 사용할 수 있습니다:
src/
└── contexts/
├── AuthContext.js
└── ThemeContext.js
예를 들어, AuthContext.js
는 다음과 같이 구현할 수 있습니다:
import React, { createContext, useState, useContext } from 'react';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = (userData) => {
setUser(userData);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
3.2 Redux 사용
더 복잡한 상태 관리가 필요한 경우 Redux를 사용할 수 있습니다:
src/
└── store/
├── actions/
├── reducers/
├── selectors/
└── index.js
4. API 통신
API 통신을 위한 구조도 중요합니다:
src/
└── services/
├── api.js
├── auth.service.js
└── user.service.js
api.js
에서는 axios 인스턴스를 생성하고, 각 서비스 파일에서 이를 사용하여 API 호출을 구현합니다:
// api.js
import axios from 'axios';
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
});
export default api;
// user.service.js
import api from './api';
export const getUserProfile = (userId) => api.get(`/users/${userId}`);
export const updateUserProfile = (userId, data) => api.put(`/users/${userId}`, data);
5. 라우팅
라우팅 구조도 확장 가능하게 설계해야 합니다:
src/
├── pages/
│ ├── Home/
│ ├── Profile/
│ └── Settings/
└── routes/
├── PrivateRoute.js
└── index.js
routes/index.js
에서 전체 라우팅 구조를 정의합니다:
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import Home from '../pages/Home';
import Profile from '../pages/Profile';
import Settings from '../pages/Settings';
const Routes = () => (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<PrivateRoute path="/profile" component={Profile} />
<PrivateRoute path="/settings" component={Settings} />
</Switch>
</Router>
);
export default Routes;
6. 환경 설정
다양한 환경(개발, 스테이징, 프로덕션)에 대응할 수 있는 구조도 필요합니다:
my-react-project/
├── .env
├── .env.development
├── .env.staging
└── .env.production
각 파일에 환경별 설정을 정의하고, process.env.REACT_APP_*
형태로 접근할 수 있습니다.
7. 테스트
테스트 파일은 각 컴포넌트나 기능 근처에 위치시키는 것이 좋습니다:
src/
└── components/
└── Button/
├── Button.js
└── Button.test.js
통합 테스트나 E2E 테스트를 위한 별도의 디렉터리를 만들 수도 있습니다:
my-react-project/
└── tests/
├── integration/
└── e2e/
8. 국제화 (i18n)
다국어 지원이 필요한 경우, 다음과 같은 구조를 사용할 수 있습니다:
src/
└── locales/
├── en/
│ └── translation.json
└── ko/
└── translation.json
react-i18 next 라이브러리를 사용하여 국제화를 구현할 수 있습니다.
확장 가능한 React 프로젝트 구조를 설계하는 것은 프로젝트의 성공에 중요한 역할을 합니다. 이 블로그에서 제시한 구조는 하나의 가이드라인일 뿐이며, 실제 프로젝트의 요구사항에 따라 적절히 수정하여 사용해야 합니다.
주요 포인트를 정리하면 다음과 같습니다:
- 명확한 폴더 구조를 사용하여 코드를 논리적으로 구성합니다.
- 컴포넌트를 재사용 가능한 단위로 분리합니다.
- 상태 관리 전략을 신중히 선택합니다.
- API 통신을 위한 별도의 서비스 레이어를 만듭니다.
- 라우팅 구조를 명확히 정의합니다.
- 환경별 설정을 관리합니다.
- 테스트 코드를 체계적으로 구성합니다.
- 필요한 경우 국제화를 고려합니다.
이러한 구조를 바탕으로 프로젝트를 시작하면, 프로젝트가 성장함에 따라 발생할 수 있는 많은 문제들을 사전에 방지할 수 있습니다. 또한, 새로운 팀원이 프로젝트에 합류했을 때도 빠르게 코드베이스를 이해하고 작업을 시작할 수 있을 것입니다.
마지막으로, 프로젝트 구조는 고정된 것이 아니라 프로젝트의 요구사항과 팀의 선호도에 따라 계속해서 발전해 나가야 한다는 점을 기억하세요. 정기적인 리팩토링과 구조 개선을 통해 프로젝트를 건강하게 유지할 수 있습니다.