들어가기에 앞서
프로젝트 폴더 구조를 설계하면서 많은 도움을 받았던 아티클을 공유하고자 합니다. Folder Structuring Techniques for Beginner to Advanced React Projects를 번역한 포스팅임을 밝힙니다. 번역에 오류가 있는 경우 댓글로 알려주시면 감사하겠습니다.
React는 코드 작성과 구조에 관해서 정해진 통념이 없는 엄청나게 유연한 라이브러리다. 하지만 어떤 규칙도 없기 때문에 React로 프로젝트를 만들 때 이 유연성이라는 특성으로 인해 프로젝트 구조를 설계하는 작업 자체가 도전 과제가 된다. 이 글에서는 모든 규모의 프로젝트에 적용할 수 있는 세 가지 폴더 구조를 다룰 것이다. 가장 간단한 폴더 구조에서 시작해서 가장 복잡한 구조 순서로 소개할 것이지만, 여러분의 프로젝트 규모에 따라서 더 간단한 방식이 더 적합할 수도 있다.
서론
폴더 구조를 정하는 방법에 대해 이야기하기 전에 한 가지 언급하고 싶은 부분이 있다. 이 글에서 다룰 폴더 구조들은 전부 src 폴더 안에 있는 파일과 폴더만을 대상으로 한다는 점이다. src 폴더가 아닌 다른 곳에 있는 파일들은 프로젝트에 따라 너무나도 다르다. 이러한 부분 때문에 모든 프로젝트에서 사용할 수 있는 단 하나의 좋은 구조란 존재하지 않고, 여러분의 프로젝트 및 사용하는 라이브러리에 따라 좋은 구조란 다를 수 밖에 없다.
첫 번째: 간단한 초급 폴더 구조
맨 처음 create-react-app을 실행하면 src 폴더에는 아무 폴더도 없다. 대부분 src 폴더 안에 components 폴더와 hooks 폴더 두 개를 처음으로 만든다. 굉장히 간단한 폴더 구조지만, 컴포넌트 개수가 10-15개 미만의 작은 프로젝트에서 사용하기에 나쁘지 않은 방법이다.
Hooks
프로젝트에서 사용하는 모든 커스텀 hook은 hooks 폴더에 저장한다. 거의 모든 프로젝트는 1개 이상의 커스텀 hook이 있고, 이러한 hook을 전부 저장할 수 있는 장소가 있다면 매우 유용하기 때문에 hooks 폴더는 프로젝트의 규모와 상관없이 도움이 된다.
Components
간단한 폴더 구조에서는 전체 애플리케이션의 모든 컴포넌트가 컴포넌트 폴더에 담기기 때문에 매우 이해하기가 쉽다. 프로젝트의 컴포넌트가 10-15개를 넘어가면서 프로젝트의 덩치가 커지면 컴포넌트 폴더를 관리하기가 굉장히 어려워질 수 있기 때문에 컴포넌트를 여러 폴더에 분산시키고, 폴더 구조에 더 많은 구조를 부여하게 된다. 하지만 소규모 프로젝트에서는 이렇게 복잡하게 만들지 않고도 단일 컴포넌트 폴더만으로 충분하다.
Tests
마지막 폴더인 테스트 폴더에는 모든 테스트 코드가 들어있다. 일반적으로 이러한 소규모 프로젝트(다시 말해 테스트 코드를 전혀 작성하지 않은 경우)는 모든 테스트를 하나의 폴더에 두는 경우가 있다. 보통은 소규모 프로젝트라면 이러한 방법이 괜찮다고 생각하지만, 프로젝트 규모가 커질 수록 다른 방법을 사용하는 편이 좋다.
장점
간단한 폴더 구조의 가장 중요한 장점은 단순하다는 점이지만, 이 점을 제외하면 기능적인 측면에서 그다지 좋은 구조라고 하기는 힘들다.
단점
이 폴더 구조는 사진, 유틸리티 함수, React 컨텍스트 등과 같은 객체를 어떻게 처리할지에 대해 사용자가 정할 수 있는 여지를 둔다. 소규모 프로젝트에서는 보통 이러한 추가 파일이 많지 않기 때문에 src 폴더의 루트에 둬도 괜찮기 때문이다. 프로젝트의 덩치가 커지면 금세 지저분해지기 때문에 소규모 프로젝트보다 큰 규모의 프로젝트에서는 최소한 중급 폴더 구조를 사용해야 한다.
두 번째: 중급 폴더 구조
위의 이미지에서 보듯이, 이 폴더 구조는 React 프로젝트에서 생각할 수 있는 거의 모든 파일의 유형을 다루는 수많은 폴더가 있다. 이러한 폴더 구조를 사용한다면 기본적으로 index.js 파일 같은 파일만 src 폴더의 루트에 있어야 한다.
프로젝트를 페이지 단위로 세분화해서 특정 페이지에 대한 모든 로직을 한 곳에 포함한다는 점이 이 폴더 구조와 간단한 폴더 구조의 또다른 중요한 차이점이다. 프로그램의 규모가 커지면 여러 폴더를 검색하고 관련없는 파일을 걸러낼 필요 없이 하나의 폴더에서 해당 페이지와 관련된 모든 데이터를 찾을 수 있다는 점이 정말 유용하다.
또한 테스트 코드가 테스트하는 특정 폴더와 파일에 맞게 그 위치가 조정된 것을 볼 수 있다. 이렇게 하면 테스트하는 코드를 더 쉽게 식별할 수 있으며, 테스트 중인 코드 옆에 테스트를 배치하면 일반적으로 테스트하기가 더 쉬워진다.
Pages
두 번째 폴더 구조에서 가장 중요한 변화는 바로 페이지 폴더가 추가되었다는 점이다. 애플리케이션의 각 페이지는 이 위치에 해당 페이지의 폴더가 있어야 한다. 각 페이지의 단일 루트 파일(일반적으로 index.js)과 해당 페이지에 속하는 모든 파일이 페이지별 폴더 안에 있어야 한다. 예를 들어 위의 이미지에서 로그인 페이지는 index.js 루트 파일과 LoginForm 컴포넌트, useLogin이라는 커스텀 hook이 있다. Login 컴포넌트와 useLogin hook은 유일하게 로그인 페이지에서만 사용되기 때문에 전역 hook이나 컴포넌트 폴더가 아닌 로그인 페이지에 저장한다.
앞에서 살펴본 (간단한) 폴더 구조에 비해 이러한 시스템이 가지는 가장 큰 이점은 페이지별 코드와 일반적인 전역 코드를 분리할 수 있다는 점이다. 모든 관련 코드가 하나의 폴더에 모여있기 때문에 애플리케이션이 수행하는 작업을 더 쉽게 이해할 수 있다.
Components
또다른 주요한 차이점은 컴포넌트 폴더 아래에 하위 폴더를 둬서 더 세분화했다는 점이다. 이러한 하위 폴더는 컴포넌트는 하나의 커다란 덩어리로 묶기보다는 여러 그룹으로 분리해 두기 때문에 꽤 유용하다. 위의 예시에서는 버튼, 모달, 카드 등의 사용자 인터페이스(UI) 요소가 포함된 ui 폴더가 포함되어 있다. 체크박스, input, 날짜 선택(date picker) 등 form과 관련된 제어를 하기 위한 form 폴더도 있다.
컴포넌트 폴더는 프로젝트의 요구사항에 맞춰서 적절하게 조정하고 세분화할 수 있지만, 더 복잡한 컴포넌트들이 페이지 폴더에 많이 있기 때문에 컴포넌트 폴더가 너무 커지지 않는 게 이상적이라고 할 수 있다.
Hooks
hooks 폴더는 단순한 폴더 구조에서 반복된 폴더 구조이다. 이 폴더는 앞서 살펴 본 hooks 폴더와 거의 비슷하지만, 애플리케이션의 모든 hook을 저장하는 것이 아니라 여러 페이지에 걸쳐서 사용되는 전역 hook만 저장한다. 특정 페이지에서만 쓰이는 hook은 해당 페이지 폴더에 저장되어 있기 때문이다.
Assets
프로젝트의 모든 사진, css 파일, 폰트 파일 등은 assets 폴더에 저장한다. 코딩과 관련 없는 모든 파일을 이 폴더에 보관한다.
Context
많은 페이지에서 사용되는 React context 파일을 context 폴더에 저장한다. 사이즈가 조금 더 큰 프로젝트에서 작업할 때 애플리케이션 전체에서 여러 컨텍스트를 사용할 가능성이 높기 때문에 단일 폴더에 context 파일을 전부 가지고 있는 것이 특히 유용하다. Redux와 같은 다른 전역 데이터 스토어를 사용하는 경우 Redux 파일을 저장하는 폴더로 해당 폴더를 바꿀 수도 있다.
Data
데이터 폴더는 assets 폴더와 비슷하지만 프로그램에서 사용되는 정보(store 아이템, 테마 정보 등)가 포함된 JSON 파일 같은 데이터를 저장하는데 사용된다. 또한 전역으로 사용하는 상수 변수가 포함된 파일도 데이터 폴더의 하위 디렉토리에 보관할 수 있다. 데이터 폴더는 환경 변수 등 상수를 많이 사용하는 경우에 유용하다.
Utils
유틸리티 폴더는 첫 번째 폴더 구조에 없는 새로운 폴더 중 마지막으로 소개할 폴더다. 포맷팅 함수 등 모든 유틸리티 기능은 이 폴더를 사용한다. 유틸리티 폴더는 꽤 단순하기 때문에 이 안에 있는 파일들도 전부 단순해야 한다. 사이드 이펙트가 있는 유틸 함수는 단순한 유틸 함수가 아닐 수 있기 때문에 개인적으로 유틸 폴더의 하위 디렉토리에는 순수 함수만 두는 것을 선호하는 편이다. 물론 모든 규칙에는 예외가 있다.
장점
각 파일은 고유의 폴더가 있다는 점이 두 번째 폴더 구조의 가장 큰 장점이다. src 루트 폴더에는 다른 파일이 거의 없어야 한다.
이제 여러분이 파일을 사용하는 페이지에 따라 파일이 배치되는데, 이러한 구조에는 또다른 큰 이점이 있다. 바로 일반적으로 코드를 이해하고, 작성하고 읽는 작업이 더 쉬워지고, 공용 컴포넌트와 hook 등의 디렉토리에 보관하는 전역 코드의 양이 줄어든다는 점이다. 프로젝트의 덩치가 커짐에 따라 함께 사용하는 파일들을 함께 저장해 두는 것이 더 중요해진다.
단점
이 방식의 가장 큰 단점은 애플리케이션이 커지면 커질 수록 페이지 폴더가 그 가치를 잃기 시작한다는 부분이다. 애플리케이션에 페이지가 많아지면 하나의 기능을 단순히 한 페이지에서 사용하는 게 아니라 여러 페이지에서 사용할 가능성이 점점 더 커지기 때문이다. 이렇게 되면 페이지 폴더의 사용 빈도가 줄고, 코드를 페이지 폴더에서 다른 폴더로 재배치해야 하기 때문에 다른 파일이 길어지게 된다.
to do 애플리케이션을 예로 들어 보자. 한 페이지에서만 할 일(task)을 관리하며 트래킹하는 경우에는 to do 페이지의 페이지 폴더에 to do 관련 모든 코드를 저장할 수 있다. 그러나 나중에 할 일의 카테고리를 분류할 수 있는 두 번째 페이지를 만든다면 더 이상 페이지 폴더에 모든 to do 파일을 보관할 수 없다. to do 정보를 표시해야 하는 페이지가 이제 두 개로 늘어났기 때문이다. 사이트가 특정 사이즈에 도달할 때까지 거의 모든 코드를 여러 페이지에 걸쳐서 공유하게 되는데, 이렇게 되면 보다 정교한 폴더 구조가 필요해진다.
세 번째: 숙련자를 위한 고급 폴더 구조
두 번째 폴더 구조와 비교하면서 위의 이미지를 슥 한 번 훑어보면 공통점이 많다는 것을 알 수 있지만, 여기에는 한 가지 큰 차이점이 있다. 바로 features 폴더다. features 폴더는 코드를 같이 묶어서 그룹화하는 더 우아한 방법으로, 기능(feature)이 겹치는 경우는 거의 없기 때문에 중급 폴더 구조에서 발생하는 페이지 폴더와 같은 문제가 생기지 않는다.
이 폴더 구조의 많은 폴더가 중급 폴더 구조와 같기 때문에 두 가지 방식에서 변경된 폴더에 대해서만 이야기하도록 하겠다.
Features
Features 폴더는 중급 폴더 구조와 고급 폴더 구조의 가장 다른 부분이다. 중급 폴더 구조의 페이지 폴더는 Features 폴더와 굉장히 유사하지만, 페이지 별로 묶는 대신 기능 별로 묶는다는 점에서 차이가 있다. 개발자라면 프로젝트에 새로운 코드를 추가한다고 할 때, 90%의 경우에 사용자 계정 추가 등 새로운 기능을 만들거나 to do 리스트의 편집 기능 추가와 같은 기존 기능을 수정하는 작업을 하기 때문에 이미이 구조가 더 이해하기 쉬울 수 밖에 없다. 각 기능에 대한 모든 코드가 한 곳에 있기 때문에 업데이트와 추가가 간편해서 코딩 작업이 한결 더 쉬워진다.
이 폴더는 각 기능(인증, to do, 프로젝트 등)에 대해 별도의 폴더가 있고, 그 폴더 안에 해당 기능과 관련된 모든 파일이 있다는 점에서 페이지 폴더의 구성과 비슷하다. 각 기능마다 다른 폴더 세트가 있다는 점이 페이지 폴더와 Features 폴더의 가장 큰 차이점이다. Features 폴더에는 (당연히 Features 폴더는 제외하고) src 폴더에 있는 모든 종류의 폴더가 동일하게 있고, 그에 더해 index.js 파일이 하나 있다. (context, hook 등) 종류 별로 코드를 분류하고, 서로 나란히 배치하면 더욱 효과적이다.
그다음, 해당 기능을 feature 폴더 외부에서 사용할 수 있도록 하는 모든 것은 index.js 파일을 통해 공개 API로 노출된다. JavaScript에서 한 파일에서 export를 해서 내보내면 어떤 파일에서든 export한 것을 사용할 수 있다. 일반적으로 작업 중인 특정 기능에 대한 private한 코드가 많이 있을 수 있다. 규모가 더 큰 프로젝트에서 기능에 대한 몇 가지 컴포넌트나 메소드만 노출하기 원한다면 이는 문제가 될 여지가 있다. 바로 이 때 index.js 파일이 유용하다. index.js 파일을 통해 기능 외부에서 사용할 수 있게 하려는 코드만 export로 내보내고, 애플리케이션에서 해당 기능을 사용할 때마다 index.js 파일에서 관련 코드를 import 해와야 한다. 이렇게 함으로써 전역 코드의 발자취가 크게 줄어들고, 한 곳에 API가 모여 있기 때문에 기능을 더 쉽게 사용할 수 있다는 이점이 있다. index.js에서 시작하지 않고는 feature 폴더에서 import를 할 수 없도록 하는 ESLint 규칙도 이를 적용하는 데 사용할 수 있다.
{
"rules": {
"no-restricted-imports": [
"error", { "patterns": ["@/features/*/*"]}
]
}
}
이 import 규칙은 absolute import를 사용하기 때문에 대규모 프로젝트에서 사용하기를 권장한다. .jsconfig나 .tsconfig 파일에서 다음의 코드를 사용해 이를 구성할 수 있다.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@features/*": ["src/features/*"]
}
}
}
Pages
페이지 폴더는 상급 폴더 구조의 또다른 중요한 변경 사항이다. 페이지의 기능에 대한 모든 로직이 feature 폴더에 있기 때문에 이제 페이지 폴더에는 페이지당 단 하나의 파일만이 있다. 몇 가지 기능 컴포넌트와 일부 generic 컴포넌트만 가지고 있다는 점을 고려하면 페이지 폴더에 있는 파일은 사실 굉장히 단순하다.
Layouts
새로운 폴더인 레이아웃 폴더는 정말 간단하다. 쉽게 말해서 레이아웃을 기반으로 하는 컴포넌트라면 어떤 컴포넌트든 이 폴더에 담으면 된다. 예를 들어서 사이드바나 내비게이션 바, 컨테이너 등이 여기에 포함된다. 애플리케이션에서 몇 가지 레이아웃만 사용하는 경우 이 폴더는 크게 필요가 없어서 컴포넌트 폴더에 레이아웃 컴포넌트를 두면 된다. 하지만 다양한 레이아웃을 사용하는 경우라면 레이아웃 폴더를 따로 만들어서 담아두면 좋다.
Lib
Lib 폴더도 간단하다. 프로젝트에서 사용하는 다양한 라이브러리의 파사드(facade)를 찾으려면 이 폴더를 보면 된다. 가령 axios 라이브러리의 경우 axios API를 기반으로 프로젝트의 자체 API를 개발할 수 있는 파일이 이 폴더에 있어서 애플리케이션에서 이를 사용할 수 있다. 다시 말해서 곧장 axios를 import 하는 게 아니라 axios와 관련된 파일을 이 폴더에서 import 해와서 쓰는 것이다.
이렇게 함으로써 애플리케이션에서 라이브러리와 관련된 코드들을 모두 통합할 수 있기 대문에 라이브러리를 업데이트하고 교체하는 작업이 훨씬 간단해진다. 또한 특정 요구사항에 맞게 서드파티 라이브러리를 맞추는 작업도 훨씬 간단해진다.
Services
서비스 폴더는 마지막으로 볼 새로운 폴더다. 외부 API와 상호작용하는 모든 코드들은 이 폴더에 있다. 대규모 프로젝트의 경우, 보통 다양한 API에 접근해야 하는데 이러한 API와 통신하는 코드를 이 폴더에 보관하면 된다. API로 상호작용하는 모든 코드들이 애플리케이션 내에서 여러 군데에 분산되어 있는 게 아니라 서비스 폴더 한 곳에 모여 있기 때문에 코드가 간결해진다.
장점
코드를 추가하고 업데이트하는 작업이 간단하다는 점은 단연 이 폴더 구조의 가장 큰 장점이다. 코드 대부분이 다양한 섹션에 나눠져 있기 때문에 새로운 기능을 추가하거나 기존 기능을 업데이트하는 것이 간단하다. 이제 파일은 private하게 은닉되어서 코드 베이스를 이해하는 데 도움을 주고, 이러한 분리가 코드 베이스를 단순화한다.
또다른 장점으로는 대부분의 비지니스 로직이 feature 폴더에 모여 있어서 feature 폴더 외부에서 코드를 비교적 쉽게 이해할 수 있다는 점이다. 다시 한 번 강조하지만 이렇게 하면 코드를 이해하고 사용하는 게 훨씬 간단해진다.
단점
시스템이 복잡해진다는 것이 이 폴더 구조의 가장 큰 단점이다. 대규모 애플리케이션의 경우 이러한 복잡성이 추가되어도 궁극적으로는 프로젝트 전반의 복잡성을 낮춰주기 때문에 큰 문제가 되지 않는다. 하지만 몇 가지 기능이나 페이지만 있을 뿐이라면 대부분의 폴더가 비어 있거나 고작 파일 몇 개만 있을 거라 이러한 폴더 구조를 채택하는 게 과하다고 할 수 있다. 따라서 추가로 폴더를 나눠야 할 필요가 있는 더 큰 사이즈의 복잡한 프로젝트에 한해 이 폴더 구조를 채택하는 편이 좋다.
결론
프로젝트의 규모와 상관없이 폴더 구조 설계는 필수이다. 위에서 소개한 세 가지 폴더 구조를 템플릿으로 해서 여러분의 프로젝트 사이즈에 맞게 수정한다면 더 깔끔하고 더 나은 코드를 쉽고 편하게 작성할 수 있을 것이다.
이외에도 폴더 구조를 주제로 같이 읽어보면 좋을 포스팅들입니다.
'번역' 카테고리의 다른 글
Tailwind CSS 스타일 재사용하는 방법 (0) | 2024.05.19 |
---|---|
[Git] 커밋은 diff가 아니라 스냅샷이다 (0) | 2024.02.18 |
React Query로 에러 처리하기 (3) | 2024.01.21 |
[React] CRA(Create React App)의 시대가 저물다 (0) | 2023.05.17 |
왜 appendChild는 DOM 노드를 이 부모에서 저 부모로 이동시키는 걸까? (5) | 2022.11.16 |