본문 바로가기

개발일지

React 프로젝트 설계 1: 나만의 폴더 구조 도입

들어가며

React 프로젝트를 처음 시작할 때, 많은 개발자들이 어떤 프로젝트 구조를 선택해야 할지 고민합니다. 저 또한 다양한 패턴을 시도해보았고, 그 과정에서 얻은 경험을 공유하고자 합니다. 이번 글에서는 Atomic 디자인 패턴에서 Container/Presentational 패턴을 거쳐, 결국 저만의 디자인 시스템을 도입하게 된 이야기를 풀어보겠습니다.

Atomic 디자인 패턴의 도입

처음에는 Atomic 디자인 패턴을 사용했습니다. Atomic 디자인은 컴포넌트를 가장 작은 단위인 'Atom'부터 시작해 'Molecule', 'Organism', 'Template', 'Page'로 계층화하는 방법론입니다. 해당 패턴은 컴포넌트의 재사용성을 높여주지만 아래와 같은 단점들로 인해 다른 디자인 패턴으로 교체하게 되었습니다.

  • 응집도 저하: 기능이 아닌 계층별로 관리하다 보니, 비슷하고 같이 사용하는 컴포넌트들이 계층별로 분리되어 응집도가 떨어지는 문제가 발생했습니다.
  • Props Drilling: 상위 컴포넌트에서 하위 컴포넌트로 props를 전달하는 과정이 너무 길었습니다. 특히 typescript 환경에서는 일일히 props의 타입을 지정해 줘야 해서 개발 시간이 증가했습니다.
  • 모호한 기준: 각 계층을 나누는 명확한 기준이 없어 어려움을 겪었습니다.
  • 예외사항: 데이터를 Page에서 내려주는 게 아닌 다른 계층에서 가져와야 하는 등의 예외사항이 있었습니다.

Container/Presentational 패턴의 도입

Atomic 디자인의 단점을 보완하기 위해 Container/Presentational 패턴을 도입했습니다. 이 패턴은 UI와 비즈니스 로직을 분리하여 컴포넌트의 역할을 명확히 하는 것이 목표입니다. 해당 패턴은 아래와 같은 장단점이 있었지만 단점이 더 크다고 생각되어 다른 디자인 패턴으로 교체하게 되었습니다.

장점

  • 재사용성: presenter 컴포넌트를 여러 container 컴포넌트와 연결해 UI 재사용
  • 명확한 구조: UI와 비즈니스 로직을 분리하여 컴포넌트의 역할을 명확히 알 수 있었습니다.

단점

  • 복잡도 증가: UI와 비즈니스 로직을 분리하는 작업이 많아지면서 오히려 복잡도가 증가했습니다.(props 전달 작업 등)
  • 많아진 파일과 길어진 파일명: 분리 작업으로 인해 파일 개수가 상당히 증가하였고, 파일명에 .presenter, .container 등을 붙이게 되어 오히려 관리와 유지보수가 어려워 졌습니다.
  • 역할 분리의 필요성?: 애초에 비즈니스 로직이 없는 기본 UI를 제외하면 생각보다 presenter를 재사용하는 경우가 많지 않았고, 혼자 개발하거나 소수의 인원으로 개발하는 경우 보통 UI와 비즈니스 로직을 같이 다루게 되어 오히려 개발에 불편을 느꼈습니다.

나만의 디자인 패턴 도입

결국, 위의 두 패턴이 가진 단점을 보완하고자 나만의 디자인 시스템을 만들게 되었습니다. 프로젝트의 특성과 개발 환경에 맞춰 최적화한 구조를 설계했습니다.

프로젝트 구조

  • app
    • [페이지 분류]
      • _component:
        • 해당 페이지 분류에서만 사용되는 컴포넌트 모음 폴더
        • 각 컴포넌트는 일반적으로 share 또는 store의 api 요청 훅과 share의 공용 컴포넌트를 조립하는 방식
      • _hook: 해당 페이지 분류에서만 사용되는 custom hook 모음 폴더
      • *Page.tsx: 기본 레이아웃에 _component와 share의 컴포넌트만을 사용해 구성(https://tak-fe.tistory.com/48 패턴 사용)
  • share
    • hook: 공용 custom hook
    • query: api 요청 관련 custom hook (위 예시에서는 rtk query를 사용했으므로 store로 대체)
    • layout: layout(https://tak-fe.tistory.com/48 패턴 레이아웃), header, footer 등 레이아웃 관련 공용 컴포넌트
    • ui: 버튼, 메뉴, 콘텐츠 박스 등 기본 ui 컴포넌트
    • util: 공용 유틸 함수 모음
    • 그 외 기능별 폴더(quiz, timeline): 여러 페이지 분류에서 사용하게 된 _component, _hook의 파일들을 기능별로 분류
  • asset: 이미지 등의 에셋
  • type: typescript 사용 시 선택적으로 사용
  • store: redux 사용 시 선택적으로 사용
  • theme: styled-component 사용 시 선택적으로 사용

특징

응집도 최대화

제가 react를 이용해 프론트엔드 개발을 하며 느꼈던 부분인데, 대부분의 비즈니스 로직은 특정 페이지 분류에 종속되는 경우가 많습니다.따라서 위와 같은 방식으로 페이지 분류별로 비즈니스 로직이 있는 컴포넌트, 커스텀 훅을 한 곳에 모아두면 해당 페이지와 관련된 모든 요소를 한 곳에서 관리할 수 있어 유지보수가 편해집니다.(해당 부분은 Nextjs의 App Router의 구조에서 영감을 얻었습니다.)

일관된 개발 흐름과 쉬운 구조 파악

초기에는 전체 프로젝트에서 공용으로 사용될 레이아웃, UI 컴포넌트, 커스텀 훅, API 요청 훅 등을 share 폴더에 구현합니다.
이후, 각 페이지의 기획 및 디자인을 확인하고, 필요한 추가 요소들을 share나 store에 추가합니다.
그런 다음, 각 페이지 분류의 _component 폴더에서 share나 store의 컴포넌트와 훅을 조립하여 필요한 컴포넌트를 만든 후, *Page.tsx 파일에 배치합니다.
이로 인해 개발 흐름이 일관되어 예측 가능해지고, 각 페이지의 구조도 쉽게 파악 가능합니다.

Props Drilling 최소화

각 페이지 분류의 _component에서 데이터를 받아 필요한 로직에 따라 가공 후, 바로 공용 컴포넌트에 전달하기 때문에 props drilling 문제가 최소화됩니다. 이는 데이터를 전달하는 계층이 줄어들어 코드의 가독성과 유지보수성을 높여줍니다.

예시로 보는 프로젝트 개발 흐름

  1. share에 다른 페이지에서도 공용으로 사용될 Header.tsx, Title.tsx, TogleButton.tsx 구현
  2. JJHSideMenu.tsx 구현 시 store/api/jjhApi의 useGetJJHCategory로 데이터를 가져와 가공 후 share/ui의 MenuGroup.tsx에 전달
  3. JJHTopicList.tsx 구현 시 store/api/jjhApi의 useGetTopicListQuery로 데이터를 가져와 가공 후 share/ui의 ContentBox.tsx와 share/timeline의 Timeline.tsx에 전달
  4. TopicAnchor.tsx 구현 시 store/api/jjhApi의 useGetTopicListQuery로 데이터를 가져와 가공 후 share/ui의 Anchor.tsx에 전달
  5. 위에서 구현한 것들을 모아 JJHTopicPage.tsx에 배치

결론

프로젝트 구조 설계는 개발자의 경험과 프로젝트의 특성에 따라 달라질 수 있습니다. 다양한 패턴을 시도해보고, 각 패턴의 장단점을 분석하여 나만의 최적의 구조를 찾아가는 과정이 중요합니다. 이번 글이 여러분의 React 프로젝트 구조 설계에 조금이나마 도움이 되었길 바랍니다.

 

다음 글

 

React 프로젝트 설계 2: 컴포넌트 유형 나누기

React 프로젝트를 진행하면서, 컴포넌트를 몇 가지 유형으로 나누어 관리하면 어떨까 하는 생각이 들었습니다. 이를 통해 코드의 가독성과 유지보수성을 높인 제 경험을 공유해보려 합니다.1. Page

tak-fe.tistory.com