[3팀 여진석] Chapter 2-2. 디자인 패턴과 함수형 프로그래밍 🦍#2
[3팀 여진석] Chapter 2-2. 디자인 패턴과 함수형 프로그래밍 🦍#2realstone2 wants to merge 52 commits intohanghae-plus:mainfrom
Conversation
- 과제의 핵심취지 및 필수 사항 추가 - 기본과 심화 과제 내용 정리 - 과제 셀프회고 및 질문 섹션 추가
|
🦍 |
- Product, Cart, Coupon, Notification 도메인 완전 분리 - 각 도메인 관련 컴포넌트, 상수, 순수함수, 상태관리 분리 - 최종 아키텍처 문서화 및 통합 테스트 계획 수립
- jotai 패키지를 dependencies에 추가 - pnpm-lock.yaml 파일 업데이트
- ProductWithUI 인터페이스 제거 및 Product로 변경 - 초기 제품 데이터 삭제 - addToCart, handleAddProduct, handleUpdateProduct, handleDeleteProduct 함수의 매개변수 타입 수정
- formatPrice 함수의 매개변수 변경 및 불필요한 코드 제거 - getRemainingStock 함수의 인자 추가로 재고 확인 로직 개선
- useDebouncedCallback 훅 구현: 지정된 시간만큼 콜백 호출 지연 - useSearchParams 훅 추가: URL의 search parameters를 관리 - useFilterSearchParams 훅 추가: searchParams를 필터링하여 반환하는 기능 구현
- 검색 파라미터 변경 시 커스텀 이벤트 발생 기능 추가 - popstate 이벤트와 함께 searchParamsChanged 이벤트 리스너 등록 및 해제 로직 구현
- .eslintrc.cjs 파일에서 React 관련 규칙을 추가하고, eslint.config.js 파일을 새로 생성하여 TypeScript 및 React 설정을 통합 - package.json에 ESLint 및 관련 플러그인 추가
…컴포넌트에서 상품 관련 로직 통합
JunilHwang
left a comment
There was a problem hiding this comment.
안녕하세요, 과제 잘 진행하셨습니다. 전체 리팩토링 의도(엔티티 vs UI 분리, 순수함수 분리, Hook 활용 등)가 명확하고, 파일/컴포넌트 분할, 상수·유틸·훅 분리 방향도 일관성 있습니다. 아래는 PR을 실제 GitHub PR Review로 바로 붙여넣어도 좋도록 구성한 종합 피드백 + 상세 피드백입니다.
주의: PR 파일 목록을 기반으로 분석했으며 일부 참조(예: 몇몇 hooks/models/utils)는 PR에 포함되지 않았거나 경로가 엮여 있어 추후 보완이 필요해 보입니다. (아래 상세에 표시)
================================================================
요약 (한줄)
- 좋은 방향: 엔티티/뷰/훅/유틸 분리, 여러 재사용 UI 컴포넌트/아이콘 추가, URL 기반 검색 훅, Jotai 도입 의도 명확
- 개선권장: Hook API(데이터/액션 분리), 사이드 이펙트(알림 등)의 의존성 역전, 상태 라이브러리 교체/패키지화 시 고려해야 할 추상계층 부족, 일부 파일/심볼 누락 또는 참조 불일치
================================================================
종합 피드백
- PR 파일 분석에서 도출한 키워드
- 모듈화: components/*, components/ui, components/product, components/coupon 등으로 분리
- 엔티티 분리: constants/, utils/, models/ (의도는 있음)
- 상태관리: jotai 도입 (package.json, pnpm-lock, tests에서 atom 사용)
- 재사용 UI: ui/Header, ui/NotificationList, icons 컴포넌트
- 테스트 통합: 테스트가 App 흐름에 맞춰 수정됨
- 결합점(주의): notifications (알림)와 useCart/useProducts 등이 직접적으로 사용/의존
- 누락/불일치: 일부 훅/모델 파일 참조(예: models/notification, hooks/useCart 등)가 PR 파일에 누락되어 있음(또는 경로 불일치)
- PullRequestBody (과제 셀프회고 / 신경 쓴 부분)에 대한 피드백 (인사이트 중심)
-
좋았던 점
- formatPrice 등 책임이 모호했던 함수의 분해 시도 (getRefinedProduct / displayPrice) — SRP 관점에서 올바른 접근입니다.
- MVVM(뷰 - 훅(뷰모델) - 모델) 관점으로 구조를 정리하려는 시도 — 컴포넌트가 팽창하지 않도록 하는 좋은 규칙입니다.
- URL 기반 상태(useSearchParams)로 UX/URL 동기화 시도 — 확장성/공유 가능한 상태로 발전시키기 좋습니다.
-
다음으로 더 생각해볼 만한 질문들 (스스로 더 확장할 수 있는 방향)
- displayPrice / getRefinedProduct처럼 변형된 엔티티 객체를 만드는 경우, 어느 계층까지 객체를 확장시키는 것이 적절한가? (예: viewOnly 필드가 많아지면 ProductWithUI 타입을 따로 정의하고 어디까지 허용할지)
- Hook 내부에서 validation/notification/UX 처리를 할 때 "직접 처리" vs "에러 던짐/콜백 위임" 중 어느 것을 더 선호하는가? 상황별 trade-off(라이브러리 작성 vs 앱 로직)에 따른 권장 방식은?
- URL 기반 필터 훅을 여러 페이지에서 재사용하려면 훅의 public API는 어떻게 설계하는 게 가장 편리할까? (예: raw URLSearchParams 반환 vs parsed 타입 안전 객체 반환 — 후자는 테스트/타입 안전성에서 이득)
- PullRequestBody의 "리뷰 받고 싶은 내용"에 대한 답변
-
Q1. Hook에서 데이터와 액션의 분리
- 의견 요약: 완전 분리는 이상적이나 현실적 트레이드오프가 있음.
- 라이브러리/엔티티 훅 (useCart 등)을 "read-only 상태 + 순수 액션(순수 함수 혹은 액션 팩토리)"로 나누면 테스트/재사용성 유리.
- 실무에서는 두 가지 혼합 패턴(스냅샷 + actions)을 많이 사용: useCart()가 { state, actions } 형태로 노출하되, actions는 내부에서 알림/유효성 처리를 하지 않고, 에러를 throw하거나 결과를 반환하여 caller가 처리하도록(또는 actions에 onError/onSuccess 옵션을 받음).
- 제안: actions는 인젝션 가능한 에러/사후처리 콜백(onSuccess/onError)이나 반환값(Promise<{ok, reason}>)을 사용하세요. (아래 코드 샘플 참조)
- 의견 요약: 완전 분리는 이상적이나 현실적 트레이드오프가 있음.
-
Q2. MVVM 패턴 적용 여부 / 선호 패턴
- 현 구조는 MVVM 성격(뷰: components, ViewModel: hooks, Model: atoms/constants/utils)을 따르고 있음. 다만 엄격한 MVVM이라면 ViewModel은 UI 의존성을 전혀 갖지 않도록(알림 등도 외부로 위임) 설계되어야 함.
- 저는 실무에서는 "Clean Architecture / Feature-sliced" + "domain entities + UI components + hooks as orchestrators"의 조합을 선호합니다. MVVM 아이디어를 유지하되, 훅은 "도메인 훅(엔티티 중심)" vs "ui/utility 훅"으로 구분하세요.
-
Q3. 함수들이 함수형패턴에 위배되는지
- 전반적으로 순수 함수/유틸 분리는 잘 되어감.
- 주의: 일부 훅/함수에서 side-effect(알림, localStorage, history manipulation)를 직접 수행하고 있어 순수성은 감소. side-effect는 훅 최상위(혹은 side-effect 전담 유틸)로 모아 호출하거나, 액션이 결과를 반환하게 하여 호출자가 side-effect를 하도록 바꾸면 함수형 원칙에 더 가깝습니다.
================================================================
상세 피드백 (파일/문제별 — 개념 정의 → 문제 → AS-IS → TO-BE)
먼저 공통 개념 정의 (요청하신 "응집도 / 결합도" 포함)
-
응집도(Cohesion) — 여기서의 정의(요청자 정의를 계승)
- 변경에 대한 파일과 코드의 추가/수정/삭제 등 동선이 얼마나 짧은가 (변경 범위를 줄이는 정도)
- 라이브러리(패키지)로 만든다고 했을 때 원하는 것만 매끄럽게 떼어낼 수 있는가
- 좋은 응집도: 하나의 변경(예: 장바구니 할인 규칙 변경)이 관련 파일/모듈 내에서 해결됨.
- 나쁜 응집도: 동일한 도메인 로직이 여러 컴포넌트/훅/유틸에 흩어져 있음.
-
결합도(Coupling)
- 모듈/함수/컴포넌트가 명확한 인터페이스로 연결되어 있는지
- 낮은 결합: 인터페이스(onSuccess/onError, callbacks, pure functions)로 연결되어 내부 구현 변경 시 주변에 영향이 적음
- 높은 결합(안 좋은 예): 내부 구현명을 직접 주입(예: addNotification을 필수 파라미터로 받는 훅).
아래 각 항목은 (1) 개념 정의 (2) 문제(왜 문제인지) (3) AS-IS(현재) (4) TO-BE(개선 예시 코드) 형식으로 정리했습니다.
- 결합도: Notification(알림)와 비즈니스 훅(useCart 등)의 결합
- 개념: 결합도를 낮추려면 상태 훅이 UI 의존(알림 처리)를 직접 하지 않고 인터페이스를 통해 알림을 위임하거나 에러를 반환해야 함.
- 문제: 코드 곳곳(addToCart, updateQuantity, addProduct 등)에서 addNotification을 직접 사용하고 있음. (PR: many addNotification calls inside App; also tests force-reset notificationsAtom)
- 결과: 훅을 다른 환경(예: 라이브러리에 넣거나 SSR)으로 옮길 때 알림 구현이 걸림. 상태 라이브러리(Jotai→Zustand/Redux) 변경 시 알림 로직도 함께 건드려야 함.
- AS-IS (간략):
- useCart (혹은 App 내부)에서 addNotification 직접 호출
- 예: addToCart(...) { if (stock<=0) { addNotification('재고 부족','error'); return } }
- TO-BE (권장)
- actions는 에러/결과를 반환하거나 옵션으로 콜백을 받음. 호출자는 알림을 담당.
- 예: useCart().addToCart(product, { onSuccess, onError }) 혹은 Promise 반환
- 구체 코드(예시):
AS-IS (예시, 현재 형태)
// inside a hook or component
const addToCart = (product) => {
if (remainingStock <= 0) {
addNotification('재고가 부족합니다!', 'error'); // direct UI side-effect
return;
}
// update cart...
addNotification('장바구니에 담았습니다', 'success');
};TO-BE (권장)
// hooks/useCart.ts (actions are pure-ish and return result)
const useCart = () => {
const addToCart = (product, opts?: { onSuccess?: () => void, onError?: (err:any) => void }) => {
const remainingStock = getRemainingStock(product);
if (remainingStock <= 0) {
opts?.onError?.(new Error('OUT_OF_STOCK'));
return { ok: false, reason: 'OUT_OF_STOCK' };
}
// perform add
return { ok: true };
};
return { cart, addToCart, ... }
};
// caller (UI component)
const { addToCart } = useCart();
const handleAdd = async (p) => {
const res = await addToCart(p);
if (!res.ok) {
addNotification('재고가 부족합니다', 'error'); // UI decides
} else {
addNotification('장바구니에 담았습니다', 'success');
}
};- 이득: useCart가 UI에 의존하지 않아 재사용/테스트가 쉬움. 다른 UI(웹/모바일/콘솔)에서도 동일 훅 재사용 가능.
- 응집도: 훅/유틸의 책임 범위와 위치
- 개념: 특정 엔티티 전용 로직은 엔티티 패키지(utils/productUtils, utils/cartUtils)에 몰아두고, 훅은 조합(or side-effect, persistence)만 하도록.
- 문제: 일부 파일(예: src/advanced/App.tsx 이전 버전)에서는 엔티티 계산, UI 로직, 로컬스토리지 저장, 알림까지 한 파일/한 훅에 혼재. PR은 많이 개선했지만 여전히 일부 컴포넌트가 여러 도메인 참조(예: ProductList 직접 getRefinedProduct + cart utils + notify).
- AS-IS: ProductList does refinedProduct = getRefinedProduct(product, cart) — 여기까지는 괜찮음. 문제는 addToCart both uses cart utils and notifications directly.
- TO-BE:
- 엔티티 유틸: calculateItemTotal(cartItem, cart), filterProductsBySearchTerm 등은 완전 순수
- 훅: useCart는 상태 + localStorage persistence + actions(위에서 설명한 방식)
- UI: 오직 렌더 + 사용자 이벤트 핸들러(이벤트를 훅에 전달)
- 추가 권장: 각 도메인(제품, 장바구니, 쿠폰, notification)을 독립적인 폴더/패키지로 분리(아래 패키징 섹션 참조).
- 상태관리 라이브러리 교체 시 시나리오 분석 (예: Jotai -> TanStack Query / Zustand / Redux)
- 요구 시나리오: 상태관리 라이브러리가 바뀌면 어떤 변화가 필요한가? (구조적 영향)
- 핵심 분석 포인트:
- public Hook API의 추상화 수준: 훅이 내부 구현(Jotai atom, Zustand store, Redux store)을 숨기면 교체가 쉬움. 반대면 전면 수정.
- 전역 atom/store를 직접 import 해서 사용하는 코드의 분포: 프로젝트 전역에서 atoms를 직접 import 한다면 대규모 변경이 필요.
- 비동기/서버 상태 vs 로컬 상태 분리: 쿼리 라이브러리(tanstack-query)는 서버 state에 적절. 엔티티의 로컬 CRUD와 서버 fetch를 혼용하지 않도록 설계해야 전환 쉬움.
- 현재 코드에서 예상 변경 작업:
- Jotai atoms (e.g. notificationsAtom, cartAtom...) → 상태 라이브러리 API로 변환 필요
- Tests: 현재 테스트 초기화에서 useAtom(notificationsAtom)로 직접 설정(테스트용 TestComponent를 렌더)하고 있음 → 상태 라이브러리 바뀌면 테스트 helper도 변경 필요
- useSearchParams: custom hook이고 독립적이라 영향 적음
- 권장 TO-BE (교체용 추상화)
- 모든 도메인 훅은 내부에서 상태 라이브러리 호출을 감추고 외부에 다음 형태의 API를 제공:
- read-only selector: useCartState() -> { cart, totalCount, totals }
- action set: useCartActions() -> { addToCart, removeFromCart, updateQuantity }
- 또는 하나의 useCart() -> { state, actions }로 유지하되 내부에서 store 구현 세부사항 감춤
- 이렇게 하면 내부 구현을 Jotai -> Zustand로 바꾸는 작업은 hooks 구현체만 바꾸면 됨.
- 모든 도메인 훅은 내부에서 상태 라이브러리 호출을 감추고 외부에 다음 형태의 API를 제공:
코드 예시: 상태 저장소를 바꿀 때 한 군데만 바꾸는 어댑터 패턴
// src/advanced/hooks/useCart.ts (public)
// 내부는 jotai/zustand/redux adapter로 구현 가능
export const useCart = () => {
// adapter를 통해 내부 구현을 숨김
return cartAdapter.useCart();
}- 패키지화(모듈화) 관점 — 응집도/결합도 평가 및 제안
- 현재 PR: 폴더 수준으로 잘 분리(product, cart, coupon, ui, utils 등) — 패키지화(예: mono-repo의 개별 패키지)로 옮기기 좋은 기초를 마련
- 체크포인트(패키지화가 매끄러운지 판단하는 기준):
- 외부로 공개해야 할 API가 명확한가? (export index.ts)
- 내부 구현(상태관리, 로컬 persistence 등)이 외부에 노출되어 있지 않은가?
- 순수 유틸/엔티티는 외부 의존성이 적은가?
- 현재 상태에서 매끄럽게 떼어낼 수 있는지 평가:
- entities/product: utils(productUtils), constants(product initial) — 응집도 높음 → 패키지로 떼어내기 쉬움.
- features/cart: cartAtoms/hooks/utils — Jotai 의존이 내부에 있으면 교체시 영향이 있으나 hooks로 잘 감췄다면 떼어내기 쉬움.
- ui 컴포넌트: Header, NotificationList, icons — 응집도 높음, 독립 UI 패키지로 떼기 쉬움.
- 권장 패키 구조 (간단)
- packages/
- entities-product (types, utils, constants)
- entities-cart (types, utils, hooks) — public API: calculateCartTotal, getRemainingStock, useCartAdapter
- features-notification (atom + useNotifications, NotificationList)
- ui (Button, Input, Header, icons)
- 각 패키지는 index.ts 로 명확한 API를 노출하고 내부 의존을 최소화
- packages/
- 응집도 적용 예 — getRefinedProduct / displayPrice 패턴 평가
- 장점: displayPrice는 UI 표기 전용으로 바뀌어 재사용 가능
- 개선:
- displayPrice의 옵션( prefix/suffix, currency, locale )을 타입으로 명확히
- getRefinedProduct가 cart와 product를 결합하여 만든 객체(ProductWithUI)는 도메인 유틸로 위치시키고, 가능한 한 부수효과가 없는 함수로 유지
AS-IS (현재)
// getRefinedProduct(product, cart) -> adds computed fields like stockRemaining, isSoldOut, displayPrice perhaps.TO-BE
type ProductViewModel = Product & { remainingStock: number, displayPrice: string }
export const getRefinedProduct = (product: Product, cart: CartItem[], opts?: PriceOptions): ProductViewModel => {
const remainingStock = calculateRemainingStock(product, cart);
return {
...product,
remainingStock,
displayPrice: displayPrice(product.price, opts)
};
}- 테스트 관련 관찰
- tests updated to import/clear Jotai atom notificationsAtom by rendering a small TestComponent that uses useAtom(notificationsAtom). 이는 Jotai 전용 방식임.
- 만약 상태 라이브러리를 바꿀 경우 테스트 초기화 코드도 바뀌어야 함. 테스트 헬퍼(예: test-utils/setupStore)로 추상화하면 좋음.
- 누락/불일치 & 작은 이슈(파일 수준)
- PR에서 확인된 누락/의심되는 점:
- tests/에서 import { notificationsAtom } from "../models/notification" 사용하지만 PR 파일 목록에 models/notification.ts 이름으로 보이는 파일은 없음 -> 실제 PR에는 notificationsAtom 정의 파일이 있어야 함.
- hooks/useCart, hooks/useProducts, hooks/useCoupons, hooks/useNotifications 등 다수 훅이 코드에 참조되지만 PR에 모두 포함되지는 않았습니다(일부는 있음, 일부는 누락). 이 경우 CI 실패 가능.
- App.tsx 수정본에서 default export는 있지만 경로 재구성으로 인해 일부 imports (예: Header path)와 실제 파일 경로가 일치하는지 확인 필요.
- 스타일/lint:
- .eslintrc / eslint.config.js 추가로 React 규칙이 강화되어 있으니 새로 추가한 파일들에 ESLint 오류/경고가 있는지 확인하세요.
- AS-IS / TO-BE 예시: useAddProduct / notification coupling (구체 코드)
- 안좋은 예(높은 결합)
const useAddProduct = (addNotification) => {
const add = (product) => {
if (!validate(product)) {
addNotification('invalid', 'error');
return;
}
// do add
}
return { add }
}- 좋은 예(낮은 결합: 콜백 인터페이스)
const useAddProduct = () => {
const add = async (product): Promise<{ ok: boolean, reason?: string }> => {
if (!validate(product)) return { ok: false, reason: 'invalid' };
// do add
return { ok: true };
}
return { add }
}
// caller
const { add } = useAddProduct();
const res = await add(product);
if (!res.ok) showNotification(res.reason);- 또는 옵션 객체(onSuccess/onError) 제공:
add(product, { onSuccess: () => {}, onError: (err) => {} })================================================================
마무리 권장사항 / 체크리스트 (우선순위 높은 것부터)
-
Hook/상태 추상화
- 모든 useXxx 훅의 public API를 명확히 문서화(useCart -> { state, actions } 형태 추천).
- actions는 알림/UI를 직접 호출하지 않도록 수정(에러/결과 반환 혹은 callback 인젝션 방식).
-
Tests 및 초기화 헬퍼 분리
- 테스트에서 상태 초기화(예: notificationsAtom 초기화) 로직을 test-helpers로 추상화. 상태 라이브러리 교체 시 이 헬퍼만 바꾸면 됨.
-
패키지화 준비
- 각 도메인의 public API(index.ts) 정의: 무엇이 외부에 노출되는지 명확히.
- 내부 의존성(Jotai 등) 숨기기: hooks만 해당 라이브러리에 의존하도록.
-
누락 파일 / 경로 점검
- PR에 참조되는 모든 훅(models/, hooks/, utils/*)이 실제로 포함되어 있는지 확인하고, import 경로가 정확한지 확인.
-
응집도/결합도 점검 절차 (PR 템플릿에 추가 권장)
- 변경한 기능에 대해 영향을 받는 파일 목록을 작성하고, 각 파일의 책임(도메인/뷰/유틸)을 표시.
- 변경에 대해 "어떤 파일만 수정하면 되는가?" 질문으로 응집도를 평가.
================================================================
추가 Q&A (PR Body에서 물어본 것들에 대한 구체적 피드백)
Q: Hook에서 데이터와 액션 분리 관련
- 권장: Actions는 UI에 독립적으로 동작하도록(결과 리턴 / 에러 throw) 설계. UI는 그 결과를 바탕으로 알림/로깅 등 수행.
- 이유: 테스트 용이성, 재사용성, 라이브러리로 배포 시 구현 의존도 최소화.
Q: MVVM 패턴 적용 여부
- 현재 구조는 MVVM의 '정신'을 따르고 있음(components = view, hooks = viewmodel, models/constants/utils = model). 다만 viewmodel(hooks)이 UI 사이드 이펙트(알림)를 직접 하는 부분이 있어 MVVM 관점에서 viewmodel이 UI에 직접 관여하는 정도가 약간 과함.
- 권장: viewmodel은 상태/로직만, view는 알림 표시 등 UI 담당으로 역할을 분리.
Q: 함수형 패턴 위배 여부
- 순수함수 분리는 많이 되어 있음(유틸). 문제는 side-effect가 혼재된 훅/함수(알림, localStorage, history.replaceState 등). 개선은 side-effect 위치를 의도적으로 모아 테스트 가능한 순수 함수와 분리하는 것.
================================================================
마무리 코멘트 (간단한 체크리스트)
- useXxx 훅들의 public API(특히 actions)의 반환값/옵션(onSuccess/onError) 설계
- Notification/Toast 등 UI side-effect를 훅 내부에서 직접 호출하지 않는 방향으로 리팩토링
- tests 초기화 헬퍼 분리 (store/test-utils)
- PR에서 참조하는 모든 훅/모델 파일이 포함/정확한 경로인지 재검토
- 패키지화 준비: 각 도메인의 index.ts로 public API 정리
================================================================
필요하면 다음 단계로 도와드리겠습니다:
- useCart / useProducts 리팩토링(데이터/액션 분리) 구체 코드 작성(리팩토링 PR 제안 코드)
- 상태 라이브러리 교체 가이드(예: Jotai → Zustand)와 어댑터 코드 스니펫 작성
- 패키지 분리(어떤 파일을 packages로 옮기고 public API는 어떻게 만들지) 제안 PR 템플릿
원하시면 제가 한 개의 훅(useCart) 또는 한 도메인(Cart)의 리팩토링 AS-IS → TO-BE 전체 코드를 만들어 드릴게요. 어느 쪽을 먼저 도와드릴까요?
| export const getRefinedProduct = (product: Product, cart: CartItem[]) => { | ||
| return { | ||
| ...product, | ||
| stock: getRemainingStock(product, cart), | ||
| } satisfies Product; | ||
| }; |
There was a problem hiding this comment.
다른분들과 조금 다르게 접근한것 같은 부분!
다른 product 유틸함수에서는 product만 들고 계산하고 싶은 목적
| export const displayPrice = ( | ||
| product: Product, | ||
| { suffix = "", prefix = "" }: { suffix?: string; prefix?: string } = {} | ||
| ): string => { | ||
| if (product.stock <= 0) { | ||
| return "SOLD OUT"; | ||
| } | ||
|
|
||
| return `${prefix}${product.price.toLocaleString()}${suffix}`; | ||
| }; | ||
|
|
- 장바구니 상품 수량 변경 기능을 useCallback 훅을 사용하여 최적화 - 재고 부족 및 수량 초과 시 알림 추가 - props로 전달받던 handleUpdateQuantity 제거
- 장바구니 상품 수량 변경 관련 코드 삭제 - CartSection에서 handleUpdateQuantity prop 제거
https://realstone2.github.io/front_6th_chapter2-2/
과제의 핵심취지
과제에서 꼭 알아가길 바라는 점
기본과제
Component에서 비즈니스 로직을 분리하기
비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기
뷰데이터와 엔티티데이터의 분리에 대한 이해
entities -> features -> UI 계층에 대한 이해
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
계산함수는 순수함수로 작성이 되었나요?
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
계산함수는 순수함수로 작성이 되었나요?
특정 Entitiy만 다루는 함수는 분리되어 있나요?
특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?
데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?
심화과제
재사용 가능한 Custom UI 컴포넌트를 만들어 보기
재사용 가능한 Custom 라이브러리 Hook을 만들어 보기
재사용 가능한 Custom 유틸 함수를 만들어 보기
그래서 엔티티와는 어떤 다른 계층적 특징을 가지는지 이해하기
UI 컴포넌트 계층과 엔티티 컴포넌트의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
엔티티 Hook과 라이브러리 훅과의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
엔티티 순수함수와 유틸리티 함수의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
과제 셀프회고
과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?
함수 분리와 책임 분담
formatPrice함수가 너무 많은 책임을 가지고 있었습니다. admin 여부 확인, 도메인 체크, 카트 데이터와의 결합, 개수 계산 등 여러 로직이 하나의 함수에 얽혀있었습니다.getRefinedProduct로 상품 데이터와 장바구니 데이터를 결합한 새로운 상품 객체를 정의하고,displayPrice로 순수하게 가격 표시 로직만 담당하도록 리팩토링했습니다.displayPrice는 admin 도메인 여부와 관계없이 독립적으로 suffix, prefix를 받아 자유롭게 가격을 노출 시킬 수 있도록 리팩토링하였습니다.MVVM 패턴으로 계층 구조의 명확한 구분
불필요한 상태, 계산 함수 제거
불필요한 상태를 사용하고 있는 ProductAccordion 컴포넌트에서 하나의 상태만 사용하도록 리팩토링
4. useSearchParams 활용한 URL 기반 상태 관리
기존의 상태 관리 방식을 URL 기반 상태 관리로 개선하여 더 나은 사용자 경험과 상태 동기화를 구현했습니다.
4.1 Zod를 활용한 product query hook 구성
4.2 기존에 단순히 searchTerm으로만 쿼리하던 로직을 향후 다른 query를 반영할 수 있도록 확장성 있게 구성
과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?
1. Hook에서 데이터와 액션의 분리가 안됨
현재
useCart와 같은 Hook에서 데이터(cart)와 액션 함수들(addToCart,updateCartItemQuantity등)을 함께 제공하고 있는데, 이 부분이 함수형 프로그래밍 원칙에 완전히 부합하는지 의문이 들었습니다.고민했던 점:
2. Handle 함수에서 validation을 담당
현재 구현에서는
handleUpdateQuantity같은 함수에서 매번 검증 코드를 작성하고 있어서 재사용성이 떨어지는 문제가 있었습니다. 아래 방법들중 하나를 채택해서 수정하는게 좋았을 것 같습니다.액션 함수 내부에서 에러 처리
전용 Handle Hook 분리
에러 핸들러 패턴
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문
위에서 얘기한 아쉬운점에 대한 내용들에 대해서 의견을 받아보고 싶습니다.
MVVM패턴으로 분리가 되었다고 생각했는데, 지금 분리된 구조가 MVVM 패턴이 맞을까요?
추가적으로 테오는 어떤 패턴을 가장 선호하시나요?
전체적으로 제가 나눠놓은 함수들이 함수형패턴에 위배되지 않는지 피드백 받고싶습니다.
클린코드와 실무에서의 개발
실무에서는 완벽히 클린코드를 사용하기는 어렵다고 생각합니다.
세부적으로 input 컴포넌트 디자인들을 나눌까도 생각했다가, 이게 실제 업무에서 리팩토링한다고 접근했을 때는 함부로 해당 input update 함수나 디자인을 억지로 묶었다가는 기획 디자인이 바뀔 때 위험할 것이라고 생각이 듭니다.
이런 내용들의 선을 어떻게 타야될까? 라는 고민이 항상 많은 것 같습니다.
이런 부분에 대해서 코치님은 어떻게 접근하시는지 궁금합니다!