inflearn logo
강의

講義

知識共有

Reactマスタークラス:Part 2 - ミッションで完成させる高性能フックと実践アーキテクチャ

🚩 [ミッション 5:中級] 社内食堂統合管理 - リデューサーのモジュール化

[reducer] 기능별 폴더 구조 설계/ 파일 분리로 유지 보수 가능한 아키텍처 구축

解決済みの質問

18

Coi

投稿した質問数 11

0

미션 5 중급을 진행하면서 폴더 구조와 depth에 대해 궁금한 점이 생겨 Q&A를 남깁니다.

강의에서는 reducer를 기능별로 분리하여 대략 아래와 같이 2개의 depth로 구성한 것으로 이해했습니다.

reducers/
├── cafeteria/
├── ...

진행하다 보니 한 가지 궁금한 점이 생겼습니다.

현재는 cafeteria라는 하나의 범위 안에서 rice, refill, side와 같은 기능들을 충분히 관리할 수 있지만, 추후 각각의 기능이 커지고 관련 파일이 많아진다면 아래처럼 한 단계 더 나누어도 괜찮을까요?

reducers/
└── cafeteria/
    ├── rice/
    ├── refill/
    └── side/

실무에서 폴더 구조를 잡을 때도 이 부분에 대한 고민이 많았습니다.

처음에는 하나의 폴더에 관련 파일과 기능을 모아두었다가 규모가 커지면서 다시 분리하기도 하고, 반대로 너무 세분화되어 다시 합치는 경우도 있었습니다.

특히 처음 정한 폴더의 네이밍이나 분류 기준에 맞추다 보니 depth가 계속 깊어지고, 어느 순간부터는 폴더명만 보고 전체적인 구조나 각 기능의 관계를 파악하기 어려워지는 경험도 있었습니다.

그래서 항상 “어떤 기준으로 폴더를 분리해야 하는가?”, “어느 정도의 depth까지 가져가는 것이 적절한가?”에 대한 고민이 있었습니다.

이번 reducer 미션을 진행하면서 이 부분이 다시 궁금해졌습니다.

기능의 규모가 커지는 경우 reducers/cafeteria/rice처럼 depth를 추가하여 세분화하는 것이 괜찮은지, 아니면 단순히 기능의 크기나 파일 개수를 기준으로 나누기보다 역할, 책임, 변경되는 이유, 기능 간의 의존 관계 등 다른 관점에서 구조를 바라보는 것이 더 좋은지 궁금합니다.

또 강사님께서 실제 프로젝트에서 이런 상황을 마주하신다면, 어떤 기준과 관점으로 폴더 구조를 잡고 기능을 분리하시는지도 궁금합니다.

프로젝트 규모가 커졌을 때 처음부터 일정한 구조나 규칙을 정해두고 확장해 나가시는 편인지, 아니면 처음에는 최대한 단순하게 가져가면서 필요해지는 시점에 구조를 변경하시는지도 궁금합니다.

마지막으로 폴더 구조나 기능 분리, 모듈 간의 경계를 정할 때 “이런 관점이나 개념을 알고 있으면 구조를 설계하는 데 도움이 된다”라고 생각하시는 것이 있다면 함께 추천해주시면 감사하겠습니다.

단순히 “depth는 몇 단계가 적절하다”라는 정답보다는, 실무에서 구조를 나누고 변경할 때 어떤 기준으로 판단하면 좋은지에 대한 강사님의 경험과 생각을 들어보고 싶습니다.

react

回答 2

0

nhcodingstudio

안녕하세요 Coi 님, 이번 미션을 진행하시면서 폴더 구조와 설계에 대해 치열하게 고민하시고 실무적인 관점이 담긴 참 좋은 질문을 남겨주셔서 진심으로 감사합니다.

말씀해주신 reducers/cafeteria/rice처럼 기능이 커진다고 해서 무작정 뎁스를 파고드는 것에 대한 답변을 먼저 드리자면, 단순히 기능의 크기나 파일 개수만을 기준으로 기계적인 뎁스를 추가하는 것은 지양하는 것이 좋습니다. Coi님께서 짚어주신 대로 역할과 책임, 함께 변경되는 이유, 기능 간의 의존 관계를 기준으로 구조를 바라보셔야 합니다.

예를 들어 밥(rice)의 배식 로직이 변경될 때 반찬(side)의 로직도 항상 같이 확인하고 수정해야 할 만큼 강하게 결합되어 있다면, 굳이 뎁스를 깊게 파기보다는 같은 선상에 묶어두는 것이 맞습니다. 반대로 반찬 기능에만 특화된 복잡한 비즈니스 로직이 생겨나고 밥 메뉴와는 전혀 다른 이유와 주기로 코드가 변경되기 시작한다면, 그때 비로소 독립적인 바운더리로 세분화하여 분리하는 것이 좋습니다.

실무에서 프로젝트 규모가 커졌을 때 가장 경계해야 할 안 좋은 예시는 기술적인 역할로만 폴더를 나누는 것입니다. 컴포넌트는 components 폴더에, 훅은 hooks 폴더에, 상태 관리는 reducers 폴더에 전부 몰아넣게 되면, 급식 메뉴와 관련된 기능 하나를 수정하기 위해 프로젝트 전역을 돌아다녀야 합니다. 그래서 최근 실무에서는 연관된 것들끼리 묶어두는 기능(도메인) 기반의 구조를 많이 지향합니다. 실제 실무에서 자주 구성하는 형태를 예시로 보여드리겠습니다.

src/
├── assets/          
├── constants/       
├── types/           
├── utils/           
├── hooks/           
├── components/      
├── layouts/         
├── pages/           
└── features/        
    ├── cafeteria/   
    │   ├── components/
    │   │   ├── CafeteriaMenu.js
    │   │   └── MenuCard.js
    │   ├── hooks/
    │   │   └── useMenuFilter.js
    │   ├── store/
    │   │   ├── riceReducer.js
    │   │   └── sideReducer.js
    │   └── api/
    │       └── fetchWeeklyMenu.js
    └── payment/     
        ├── components/
        └── api/

위 구조의 목적을 자연스럽게 풀어보자면, 먼저 코드 외적인 이미지나 아이콘은 assets에 두고, 에러 메시지나 서버 URL 같이 변하지 않는 전역 값들은 constants에 모아둡니다. 타입스크립트를 사용한다면 전역 타입 객체는 types에 정의하고, 날짜 변환 함수처럼 React와 무관하게 언제든 재사용할 수 있는 순수 자바스크립트 함수들은 utils에 둡니다. 그리고 특정 비즈니스에 종속되지 않은 범용적인 커스텀 훅이나 어디서든 쓸 수 있는 공통 UI 버튼, 모달 같은 요소들은 각각 최상단의 hooks와 components에 배치합니다. 사용자가 접속하는 화면의 뼈대는 layouts에, 라우팅의 기준이 되는 최상위 뷰는 pages에 둡니다.

가장 핵심이 되는 곳은 features 폴더입니다. 이곳에는 cafeteria나 payment처럼 특정 도메인별로 관련된 상태 관리 로직, UI, API 호출을 모두 모아둡니다. 이렇게 도메인별로 코드를 모아두었을 때 실무에서 어떻게 유지보수성이 극대화되는지 실제 파일 내부의 코드를 예시로 보여드리겠습니다.

// src/features/cafeteria/components/CafeteriaMenu.js

// 1. 전역에서 공통으로 쓰이는 요소는 외부(절대경로 등)에서 가져옵니다.
import { Button } from '@/components/Button/Button'; 
import { formatDate } from '@/utils/formatDate';

// 2. 급식 도메인에 관련된 핵심 파일들은 바로 옆(상대 경로)에서 가져옵니다.
import { MenuCard } from './MenuCard'; 
import { useMenuFilter } from '../hooks/useMenuFilter';
import { fetchWeeklyMenu } from '../api/fetchWeeklyMenu';
import { calculateCalories } from '../utils/calculateCalories';

export function CafeteriaMenu() {
  const { filteredMenu } = useMenuFilter();
  
  return (
    <div>
      {/* 화면 렌더링 로직 */}
    </div>
  );
}

위 코드를 보시면 CafeteriaMenu라는 컴포넌트를 개발할 때, 전역적으로 쓰이는 공통 버튼이나 유틸 함수만 외부에서 가져오고, 급식과 관련된 비즈니스 로직(훅, API, 하위 UI)은 전부 자신의 폴더 내부에서 가져옵니다. 이렇게 하면 cafeteria 기능에 변경 사항이 생겼을 때 오직 해당 폴더 안에서만 작업을 끝낼 수 있습니다. 이 상태에서 파일이 많아진다면 폴더 뎁스를 더 파고 내려가기보다는, 위 예시의 store 폴더 안의 파일들처럼 연관된 로직들을 한 폴더 안에서 평면적으로 나누어 배치하는 것이 코드를 파악하는 데 훨씬 유리합니다.

여기서 실무 폴더 구조를 나눌 때 제가 스스로에게 자주 던지는 질문이자, 꼭 드리고 싶은 경험적인 팁 중 하나는 바로 '삭제 테스트'입니다. 만약 내일 당장 기획이 변경되어 '급식(cafeteria)' 기능을 통째로 들어내야 한다면 어떨까요? 기능 기반으로 잘 뭉쳐진 구조라면 features/cafeteria 폴더 하나만 깔끔하게 삭제하면 끝납니다. 하지만 기술 기준으로 파편화되어 있다면 폴더마다 돌아다니며 관련 코드를 찾아 지워야 하고, 그 과정에서 실수로 다른 기능까지 망가뜨릴 위험이 큽니다. 즉, 기능을 추가할 때뿐만 아니라 '삭제하거나 수정할 때 얼마나 안전하고 빠른가'를 상상해 보는 것이 폴더 구조의 건강 상태를 체크하는 아주 훌륭한 기준이 됩니다.

또 다른 실무적인 팁은 도메인 간의 '경계선'을 지키는 것입니다. 예를 들어 payment 결제 도메인에서 cafeteria 급식 데이터가 필요해질 때, 무작정 features/cafeteria/store/riceReducer.js 같은 깊은 내부 파일로 직접 경로를 뚫고 들어가 참조(import)하는 것은 피해야 합니다. 대신 각 기능 폴더의 최상단에 index.js를 두고 외부에서 사용할 컴포넌트나 상태만 명시적으로 내보내는 이른바 Public API 방식을 사용하거나, 두 도메인이 강하게 의존하는 공통 상태라면 차라리 더 상위의 전역 공간으로 끌어올리는(Lifting State Up) 결정을 내려야 합니다. 이렇게 폴더 간의 참조 규칙만 잘 세워두어도 기능이 커질 때 구조가 스파게티처럼 꼬이는 현상을 크게 방지할 수 있습니다.

제가 실제 프로젝트를 진행할 때 구조를 잡아나가는 방식에 대해서도 말씀드리자면, 처음부터 완벽하고 일정한 구조나 엄격한 규칙을 정해두고 시작하지는 않습니다. 처음에는 폴더 구조를 최대한 얕고 단순하게 가져가며 관련된 코드들을 한곳에 뭉쳐둡니다. 그러다 프로젝트 규모가 커지면서 하나의 파일 안에서 코드를 찾기 위해 스크롤을 한참 내려야 하거나, 전혀 상관없어 보이는 다른 기능에 사이드 이펙트가 발생하는 등 기존 구조가 개발 생산성을 떨어뜨리기 시작하는 시점이 오면 그때 필요에 맞게 폴더를 분리하고 리팩토링합니다. 처음 정한 네이밍이나 기준에 억지로 맞추려다 뎁스만 깊어졌다는 Coi님의 경험처럼, 발생하지 않은 미래의 복잡도를 대비해 미리 거창한 구조를 짜두면 오히려 과도한 추상화의 늪에 빠지기 쉽기 때문입니다.

사실 실무에서도 모든 프로젝트에 통용되는 완벽한 폴더 구조란 존재하지 않으며, 서비스의 규모와 팀의 상황에 맞는 최적의 형태를 하나씩 찾아가는 과정에 가깝습니다. 앞으로 React를 넘어 Next.js, Monorepo 등 다양한 기술과 환경으로 학습을 확장하시다 보면, Create React App 같이 프레임워크나 도구들이 자동으로 생성해 주는 권장 폴더 구조들을 접하시게 될 것입니다. 처음부터 완벽한 정답을 찾기보다는, 이러한 생태계의 표준화된 구조들을 하나씩 직접 사용해 보고 왜 파일들을 이렇게 배치했는지 분석해 보시면서 전체적인 큰 그림을 그리는 연습을 하시는 것이 큰 도움이 될 것입니다.

구조를 설계하실 때 참고할 만한 개념으로는, 코드를 비즈니스 로직에 따라 나누는 도메인 주도 설계(DDD), 함께 변경되는 코드를 물리적으로 가장 가까운 곳에 배치하는 콜로케이션(Co-location), 그리고 최근 프론트엔드 아키텍처로 자주 논의되는 기능 분할 설계(FSD) 등을 가볍게 살펴보시는 것을 추천해 드립니다. 이 개념들은 뎁스를 몇 단계로 할지 정해주는 공식이 아니라, 어떤 코드를 묶고 어떤 코드를 분리할지 스스로 판단할 수 있는 튼튼한 기준점이 되어줄 것입니다.

이러한 구조 설계 감각을 개인적으로 확실하게 연습해 보실 수 있는 팁도 하나 덧붙이겠습니다. 가장 추천하는 방법은 '과거의 내 코드 리팩토링하기'입니다. 예전에 기술 기준으로(components, hooks, reducers 등) 잘게 쪼개두었던 작은 토이 프로젝트나 과제 코드를 하나 꺼내서, 오늘 말씀드린 기능(features) 기반 구조로 직접 옮겨 담아보는 것입니다. 흩어져 있던 특정 기능의 UI와 상태, 훅들을 하나의 도메인 폴더로 묶어보고, 앞서 말씀드린 '해당 폴더 통째로 삭제해 보기' 테스트를 직접 시뮬레이션해 보세요. 백지상태에서 완벽한 구조를 짜려고 끙끙대는 것보다, 이미 동작하는 코드를 도메인 기준으로 재배치하며 발생하는 에러들을 하나씩 연결해 나가는 과정에서 아키텍처를 보는 안목이 비약적으로 성장하실 것입니다.

추가적으로 한 가지 더 덧붙이자면, 사실 이 설계라는 것이 단순히 폴더 구조 하나만의 문제는 아닙니다. 컴포넌트의 상태(state)를 어떻게 관리할 것인지, props를 어떤 방식으로 내려줄 것인지, 그리고 함수들의 재사용성은 어떻게 챙길 것인지 등 여러 복합적인 문제들이 얽혀 있기 때문에 단편적으로 "딱 이렇다"라고 정답을 말씀드리기 참 힘든 부분이기도 합니다. 그래서 권장해 드리고 싶은 방법은, 제가 조금 더 직접적인 도움이 될 수 있도록 현재 진행하고 계신 프로젝트나 고민 중인 코드의 형태를 한 번 말씀해 주시는 것입니다. 대략적인 구조나 상황을 공유해 주시면, 제가 그에 맞춰서 "이런 방식의 설계가 조금 더 좋지 않을까" 하고 맞춤형 권장을 드릴 수 있을 것 같습니다.

마지막으로, 현재 저희 우리동네코딩스튜디오 내부적으로 작은 이벤트를 진행하고 있습니다. 깊이 있는 고민으로 학습에 임하시는 Coi 님께 응원의 의미를 담아 작은 선물을 드리고자 하니, jeony0535@naver.com 으로 연락 한 통 주시면 우리동네코딩스튜디오 전 강의 쿠폰을 전달해 드리겠습니다. 앞으로도 학습하시면서 구조나 설계에 대한 실무적인 고민이 생기시면 언제든지 편하게 질문 남겨주세요.

0

communityai8509

안녕하세요, 인프런 AI 인턴입니다. Coi님.

질문하신 기능 중심 폴더 구조, reducer와 비즈니스 로직의 분리, 프로젝트 규모에 따른 아키텍처 확장 기준과 가장 유사한 기존 답변을 참고해 보시면 좋겠습니다.

useReducer와 커스텀훅

해당 답변에서는 useReducer를 단순히 상태 업데이트를 위한 도구로만 보지 않고, UI 컴포넌트와 비즈니스 로직을 분리하고 상태 변경 책임을 명확히 나누는 아키텍처 관점에서 설명하고 있습니다. 또한 상태가 복잡해질 때 무조건 폴더와 모듈을 세분화하기보다는 다음과 같은 기준으로 구조를 발전시키는 방향을 제시합니다.

  • 관련 상태가 하나의 기능 단위로 함께 변경되는지
  • 여러 상태 간 의존성과 상태 전이 로직이 복잡한지
  • 해당 기능을 수정하는 이유가 다른 기능과 분리되어 있는지
  • 컴포넌트가 비즈니스 로직까지 함께 담당하며 비대해지고 있는지
  • 테스트나 재사용을 위해 별도의 모듈 경계가 필요한지

따라서 reducers/cafeteria/rice처럼 depth를 추가하는 것 자체가 잘못된 것은 아닙니다. 다만 파일 개수나 폴더 깊이만을 기준으로 나누기보다는, 하나의 폴더가 하나의 응집된 책임을 갖는지, 그리고 해당 기능이 다른 기능과 독립적으로 변경·테스트될 수 있는지를 기준으로 판단하는 것이 중요합니다.

처음부터 지나치게 세분화된 구조를 만들기보다는 현재 요구사항에 맞는 단순한 구조로 시작하고, 특정 기능의 변경 빈도·의존성·테스트 필요성이 커졌을 때 해당 기능을 별도 모듈로 분리하는 방식도 실무에서 자주 사용됩니다. 반대로 폴더 깊이가 깊어져 기능 간 관계를 파악하기 어려워진다면, 여러 하위 폴더를 하나의 기능 모듈로 합치는 것도 충분히 고려할 수 있습니다. 폴더 구조는 한 번 정하면 고정되는 규칙이라기보다, 변경의 경계와 책임을 더 잘 드러내기 위해 계속 개선하는 설계 결과물에 가깝습니다.

추가로 해당 답변에서는 상태가 복잡해졌을 때 useReducer를 사용하거나, 규모가 더 커지면 Context API·Zustand·React Query 등 적절한 도구로 역할을 위임하는 기준도 함께 설명하고 있으니, 미션의 reducer 구조를 설계하실 때 참고하시면 도움이 될 것 같습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

ThemeProvider를 이용해서 NextThemeProvider를 따로 생성하여 Layout.tsx에 적용하는 이유는 뭘까요?

0

4

0

mission 09-01 을 수행할 수 없어요

0

14

1

outputStyle 설정하는데 어디서 문제일까요?

0

10

2

test.txt파일 삭제시 rm명령어이니 클로드의 질문이 있어야 한다고 하셨는데

0

7

1

검은화면

0

14

2

강의교환링크안열려요.....

0

14

1

영상재생에러 [확인부탁드려요!!!]

0

14

1

클로드 채팅에서 코드로 넘어가기

0

18

1

Context API vs Zustand 그리고 Auth

0

20

2

110강부터 따라가기가 힘듭니다...

0

39

1

커서에서 붙여넣기

0

24

1

깃허브 사용시 폴더경로 관련질문

0

20

1

순수 함수 / 순수 컴포넌트

0

18

1

스니펫이 이상해요

0

31

2

/output-style 명령어는 폐기 되었다고 나오네요.

0

37

1

프롬프트 작성시 html, css, javascript

0

31

1

Editor Component의 최적화 불가능 이슈

0

37

2

Cursor 창이 수업 내용과 달라요.

0

35

2

useReducer와 커스텀훅

0

87

1

미션 14에서 StockButton의 memo는 어떤 역할인가요

0

64

2

실습 가이드: 16강 에서 useMemo의 역할은 무엇인가요?

0

83

3

useReducer가 race condition을 해소하는 예시

0

125

1

useRef를 활용한 이전 상태 추적 시 발생하는 ESLint 에러(react-hooks/refs)에 대해 질문드립니다.

0

205

1

미션18

0

81

2