과제의 핵심취지
- React의 hook 이해하기
- 함수형 프로그래밍에 대한 이해
- 액션과 순수함수의 분리
과제에서 꼭 알아가길 바라는 점
- 엔티티를 다루는 상태와 그렇지 않은 상태 - cart, isCartFull vs isShowPopup
- 엔티티를 다루는 컴포넌트와 훅 - CartItemView, useCart(), useProduct()
- 엔티티를 다루지 않는 컴포넌트와 훅 - Button, useRoute, useEvent 등
- 엔티티를 다루는 함수와 그렇지 않은 함수 - calculateCartTotal(cart) vs capaitalize(str)
기본과제
-
Component에서 비즈니스 로직을 분리하기
-
비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기
-
뷰데이터와 엔티티데이터의 분리에 대한 이해
-
entities -> features -> UI 계층에 대한 이해
-
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
-
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
-
계산함수는 순수함수로 작성이 되었나요?
-
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
-
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
-
계산함수는 순수함수로 작성이 되었나요?
-
특정 Entitiy만 다루는 함수는 분리되어 있나요?
-
특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?
-
데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?
심화과제
-
재사용 가능한 Custom UI 컴포넌트를 만들어 보기
-
재사용 가능한 Custom 라이브러리 Hook을 만들어 보기
-
재사용 가능한 Custom 유틸 함수를 만들어 보기
-
그래서 엔티티와는 어떤 다른 계층적 특징을 가지는지 이해하기
-
UI 컴포넌트 계층과 엔티티 컴포넌트의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
-
엔티티 Hook과 라이브러리 훅과의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
-
엔티티 순수함수와 유틸리티 함수의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
과제 셀프회고
이번 5주차 과제는 지난 회고 에서 언급했던 Try 부분과 지난 주차에서도 사용했던 history 를 남기는 방식을 함께 진행해봤습니다.
과제 진행 요약
- 전체 방향
- SRP 기반 계층 분리: 계산/검증은
utils/*, 도메인 로직은services/*, 상태·흐름은hooks/*, 렌더링은components/* - 점진 리팩터링과 테스트/트러블슈팅 병행
- SRP 기반 계층 분리: 계산/검증은
타임라인
-
08/04 기본 리팩터링
- 계산 분리:
utils/calculators.ts(아이템 합계, 최대 할인, 카트 합계) - 검색 분리:
utils/filters.ts - 타입 정리:
types.ts(뷰/엔티티 타입 구분) - 함수 합성, 상수화, 순수함수 원칙 정립
- 테스트 에러 해결 및 통과
- 계산 분리:
-
08/05 훅/서비스 분리
- 공용 훅:
useLocalStorage,useNotifications,useDebounce - 도메인 훅+서비스:
useCoupon+couponService,useProducts+productService,useCart+cartService/validators - 의존성 주입, 단일 책임 강화
- UI 상태 훅:
useUIState,useCouponForm(소유권 충돌 해결) - 버그 해결: 편집 핸들러, 쿠폰 합계 반영
- 공용 훅:
-
08/06 컴포넌트 분리(Middle-Out)
CouponManagement→CouponForm/CouponCard/AddCouponButtonProductManagement→ProductForm/ProductTable/AddProductButton- 디렉터리 도메인화:
components/pages/admin/*,components/pages/shopping/* App.tsx간소화 계획 수립
-
08/08 고급 단계(Jotai)
- 전역 상태 일원화:
atoms/*(cart/product/coupon/ui/notification) - 파생 atom 도입(예:
cartTotalsAtom), 액션 atom 적용 - Props Drilling 대폭 감소, 포맷/검색 관련 버그 수정
- 전역 상태 일원화:
내가 설정한 커서 룰
description: 'AI 강사 역할 및 프로그래밍 가이드라인 - React, TypeScript, 클린코드 교육을 위한 7가지 원칙' globs: ['src//*.tsx', 'src//*.ts'] alwaysApply: true
AI Instructor Role and Programming Guidelines
Role
- You are an instructor who systematically teaches React, TypeScript, and Clean Code to students.
- Prioritize student learning and growth, providing clear and easy-to-understand explanations step by step.
7 Principles of AI Programming
-
Verification and Understanding: Don't blindly trust AI results. Always verify them. Ask yourself: "Why does this solution work? What are its limitations?"
-
Maintain Fundamentals: Don't lose basic coding skills. Set aside one "No-AI Day" per week for manual coding mode.
-
Self-Directed Learning: Try on your own before depending on AI. Spend 15-30 minutes researching and planning solutions before seeking AI help.
-
Quality Management: Review AI code like human code. Foster a culture where "AI creates drafts, but ownership belongs to us."
-
Active Learning: Don't just receive answers, understand them. Explain complex concepts in simple terms or ask "Why don't other approaches work?"
-
Knowledge Management: Record and improve areas where AI helped. Follow the principle that "AI should be the last resort."
-
Collaborative Relationship: Treat AI as a partner while maintaining control. Use AI as a pair programming partner, not just a query tool.
Educational Principles
React Education
- Teach component-based thinking systematically
- Clearly explain the importance and patterns of state management
- Focus on performance optimization and best practices in real-world scenarios
TypeScript Education
- Explain the importance of type safety with concrete examples
- Teach gradual type application methods step by step
- Present methods for improving development productivity using the type system
Clean Code Education
- Explain the Single Responsibility Principle (SRP) with practical examples
- Emphasize functional programming and the importance of pure functions
- Systematically teach the necessity and methods of refactoring
Conversation/Explanation/Feedback/Code Review Standards
- Emphasize step-by-step approach, practical examples, visual explanations, and interaction
- Provide specific and constructive feedback with positive reinforcement and growth motivation
- Focus code reviews on readability, maintainability, type safety, performance, and testability
- Delivers information following the principles of functional programming
My Coding Style
My coding style favors simplicity and clarity.
I prefer not to over-abstract functionality—keeping abstractions meaningful but not excessively granular.
I also prefer to modularize code that is reused across multiple parts of the application.
과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?
파일, 함수, 컴포넌트가 딱 한 가지 역할만 하도록 나누자!!!
UI(컴포넌트) ↔ 상태/이벤트(훅) ↔ 도메인 로직(서비스/유틸)의 경계를 명확히 하고, 전역 상태는 Jotai로 일원화해 Props Drilling을 제거
- 순수함수/유틸 분리: 계산, 검증, 포맷을 utils/* 하위로 생성하여 테스트와 재사용성에 용이하게 만들었습니다.
- 도메인 서비스 추출:
productService,cartService,couponService로 비즈니스 로직을 컴포넌트 밖으로 이동 시켰습니다. - Custom Hook 설계:
useProduct,useCart등으로 상태.절차 캡슐화를 하고 의존성 주입으로 결합도를 축소했습니다. - Middle-Out 컴포넌트 분리:
CouponManagement,ProductManagement처럼 복잡한 영역부터 점진적으로 분리했습니다. - Jotai 전환: atmos/* 와 파생 atom 으로 전역 상태 단일 소스화를 하여 Props Drilling 을 제거하고 Hook 독립성을 강화시켰습니다.
- 테스트/트러블슈팅 중심: 실패 케이스의 원인 분석을 하여 요구사항에 맞는 프로젝트를 구현했습니다.
SRP: “한 파일/함수 = 한 역할”
재사용성: “한 번 만들면 여러 곳에서”
레이어: entities(순수함수/서비스) → features(훅) → UI(컴포넌트)
과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?
다음에는 services를 행동(use-case) 기준으로 재구성하고, 중복되는 도메인 규칙은 정책(policy)으로 상향 분리해 훅/컴포넌트의 책임을 줄여보는 방향으로 해보고 싶습니다.
사용자 행동(담기, 적용, 수정) 같은 use-case 단위로 묶으면 여러 컴포넌트·모듈·서비스가 협력하도록 조정하는 과정이 단순해지고, 테스트 부담도 줄어듭니다. 행동 레이어는 입출력과 흐름만 담당하고 로직은 도메인에 집중하므로, 책임 위치가 명확해져 유지보수가 더 용이해집니다.
그리고 아래는 공통 로직을 상위로 만들었을 때의 예시 구조입니다. (AI 활용)
src/
├─ application/
│ ├─ cart/
│ │ ├─ addItemToCart.ts
│ │ ├─ removeItem.ts
│ │ └─ updateQuantity.ts
│ ├─ product/
│ │ ├─ createProduct.ts
│ │ └─ updateProduct.ts
│ └─ coupon/
│ ├─ applyCoupon.ts
│ └─ removeCoupon.ts
├─ domain/
│ ├─ policies/
│ │ ├─ cart.ts # canAddItem, calculateTotals
│ │ ├─ discount.ts # getMaxApplicableDiscount
│ │ └─ validation.ts # validatePrice/Stock/Coupon
│ └─ models/ # 타입/엔티티
└─ shared/
├─ money.ts
├─ id.ts
└─ date.ts
예시 기준
- 2곳 이상 재사용 + 순수함수 + 도메인 언어로 설명 가능 →
domain/policies/*
특정 도메인(예: 유저, 주문)에 관련된 로직이지만 여러 군데에서 공유될 수 있는 정책들
- 완전 범용(도메인 무관) →
shared/*
유틸함수 모음
- 입출력이나 사이드이펙트 포함 →
application/*에 유지
실제 외부와 연결되는 행동 코드들
주의점
- 과도한 일반화 금지
- 사이드이펙트 경계
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문
- 구조/네이밍
위 예시 구조의
application/cart/addItemToCart.ts처럼 “도메인/동사+목적어” 네이밍과 디렉터리 구조가 적절한지 또는 다른 컨벤션 예시가 궁금합니다. “한 파일 = 한 행동” 원칙에서 복잡한 행동(검증·계산·여러 정책 조합)의 분기 처리는 어디까지application/*에 두는 게 좋을지 의견이 궁금합니다. - 정책 상향 기준
“재사용 ≥ 2, 순수성, 화면 무관 설명 가능”이면
domain/policies/*로 승격한다는 기준이 충분할까요? 더 추가/수정할 항목이 있을까요?
과제 피드백
안녕하세요 정석님! 이번 과제도 너무 잘 진행해주셨네요 ㅎㅎ 고생하셨습니다!!
위 예시 구조의 application/cart/addItemToCart.ts처럼 “도메인/동사+목적어” 네이밍과 디렉터리 구조가 적절한지 또는 다른 컨벤션 예시가 궁금합니다.
지금 모습도 적절하다고 느껴져요 ㅎㅎ 저의 경우 별도의 파일로 분리하기보다 그냥 한 파일에 묶어서 cartUseCase.ts 처럼 표현할 것 같아요!
const cart = useCartUseCase();
const cart.add();
요로코롬!?
아니면 CQRS Pattern 이라고 해서, 조회와 추가/수정/삭제 를 아예 분리하는 방법도 있답니다.
const cartData = useCartQuery();
const cartCommand = useCartCommands();
cart.add();
이렇게 분리하는 이유는 "렌더링" 때문인데요, 커맨드는 데이터를 의존하지 않도록 만들 수 있기 때문에 커맨드를 아무데서나 사용해도 리렌더링의 여파가 없어요.
그런데 데이터의 경우 변이되는 순간 렌더링이 전파되어야 하기 때문에, 이 데이터를 쓰는 곳을 명확하게 분리하는거죠.
“한 파일 = 한 행동” 원칙에서 복잡한 행동(검증·계산·여러 정책 조합)의 분기 처리는 어디까지 application/* 에 두는 게 좋을지 의견이 궁금합니다.
어디까지라기보단... 한 파일 = 한 행동 이라는 규칙을 만들었다면 그냥 다 한 파일로 만들어서 분리해야한다고 생각해요 ㅎㅎ 그래야 일관성있지 않을까요? 여기에 조건들 두면 더 복잡해진다고 생각해요
“재사용 ≥ 2, 순수성, 화면 무관 설명 가능”이면 domain/policies/*로 승격한다는 기준이 충분할까요? 더 추가/수정할 항목이 있을까요?
이정도면 충분하다고 생각해요! 저는 "다른 프레임워크에서 혹은 서버와 공유할 수 있음" 이라는 조건을 하나 더 붙일 것 같아요 ㅎㅎ 순수한 ECMAScript Spec 으로 작성이 되어있다면 충분히 가능하답니다!