hyunzsu 님의 상세페이지[8팀 현지수] Chapter 2-1. 클린코드와 리팩토링

과제 체크포인트

기본과제

  • 코드가 Prettier를 통해 일관된 포맷팅이 적용되어 있는가?
  • 적절한 줄바꿈과 주석을 사용하여 코드의 논리적 단위를 명확히 구분했는가?
  • 변수명과 함수명이 그 역할을 명확히 나타내며, 일관된 네이밍 규칙을 따르는가?
  • 매직 넘버와 문자열을 의미 있는 상수로 추출했는가?
  • 중복 코드를 제거하고 재사용 가능한 형태로 리팩토링했는가?
  • 함수가 단일 책임 원칙을 따르며, 한 가지 작업만 수행하는가?
  • 조건문과 반복문이 간결하고 명확한가? 복잡한 조건을 함수로 추출했는가?
  • 코드의 배치가 의존성과 실행 흐름에 따라 논리적으로 구성되어 있는가?
  • 연관된 코드를 의미 있는 함수나 모듈로 그룹화했는가?
  • ES6+ 문법을 활용하여 코드를 더 간결하고 명확하게 작성했는가?
  • 전역 상태와 부수 효과(side effects)를 최소화했는가?
  • 에러 처리와 예외 상황을 명확히 고려하고 처리했는가?
  • 코드 자체가 자기 문서화되어 있어, 주석 없이도 의도를 파악할 수 있는가?
  • 비즈니스 로직과 UI 로직이 적절히 분리되어 있는가?
  • 코드의 각 부분이 테스트 가능하도록 구조화되어 있는가?
  • 성능 개선을 위해 불필요한 연산이나 렌더링을 제거했는가?
  • 새로운 기능 추가나 변경이 기존 코드에 미치는 영향을 최소화했는가?
  • 코드 리뷰를 통해 다른 개발자들의 피드백을 반영하고 개선했는가?
  • (핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?

심화과제

  • 변경한 구조와 코드가 기존의 코드보다 가독성이 높고 이해하기 쉬운가?
  • 변경한 구조와 코드가 기존의 코드보다 기능을 수정하거나 확장하기에 용이한가?
  • 변경한 구조와 코드가 기존의 코드보다 테스트를 하기에 더 용이한가?
  • 변경한 구조와 코드가 기존의 모든 기능은 그대로 유지했는가?
  • (핵심!) 변경한 구조와 코드를 새로운 한번에 새로만들지 않고 점진적으로 개선했는가?

과제 셀프회고

https://hyunzsu.github.io/front_6th_chapter2-1/

과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?

구분기본과제 (Vanilla JS)심화과제 (React + TS)
상태관리전역 상태 객체useReducer + 도메인별 상태
UI 렌더링DOM 직접 조작React 컴포넌트
타입 안정성JSDoc 주석TypeScript
패턴MVC (Controller)Flux (Hooks + Reducers)
데이터 흐름명령형선언형
📍 기본 과제

1. 전체 아키텍처 개요

src/basic/
├── main.basic.js            
├── __tests__/              
├── features/               # 도메인별 기능 모듈
│   ├── cart/              
│   ├── order/             
│   ├── product/           
│   ├── promotion/         
│   └── index.js           
└── shared/                # 공통 모듈
    ├── components/  
    ├── constants/      
    ├── core/             
    ├── utils/            
    └── index.js

2. 메인 엔트리 포인트 분석

main.basic.js

function main() {
  // 앱 데이터 초기화
  initializeAppData();

  // DOM 구조 생성
  createDOMStructure();

  // 초기 UI 업데이트
  ProductController.updateSelectOptions();
  CartController.updateCartDisplay();

  // 이벤트 핸들러 초기화
  initializeEvents();

  // 프로모션 시스템 시작
  LightningSaleService.startLightningSaleSystem(/* ... */);
  SuggestedSaleService.startSuggestedSaleSystem(/* ... */);
}

main();
  • 절차적 초기화: 순서대로 앱을 초기화
  • 전역 상태 기반: 비즈니스 상태를 전역으로 관리
  • DOM 직접 조작: React 없이 순수 DOM API 사용

3. 공통 모듈 분석 (shared/)

A. 상태 관리 핵심 (core/)

business-state.js - 전역 상태 저장소

export const businessState = {
  products: [],
  bonusPoints: 0,
  itemCount: 0,
  totalAmount: 0,
  lastSelectedProductId: null,
};

export function getProducts() {
  return businessState.products;
}

export function getProductById(id) {
  return businessState.products.find((product) => product.id === id);
}

dom-refs.js - DOM 요소 참조 관리

export const domRefs = {
  stockInfo: null,
  productSelect: null,
  addButton: null,
  cartDisplay: null,
  totalDisplay: null,
  // ... 기타 DOM 요소들
};

export function initializeDOMReferences() {
  domRefs.stockInfo = document.getElementById('stock-status');
  domRefs.productSelect = document.getElementById('product-select');
  // ... DOM 요소 참조 설정
}

B. DOM 구조 생성 (core/)

dom.js - 전체 DOM 구조를 프로그래밍 방식으로 생성

export function createDOMStructure() {
  const root = document.getElementById('app');

  // 컴포넌트 생성
  const header = Header();
  const mainGrid = MainGrid();
  const leftColumn = LeftColumn();
  const productPanel = ProductPanel();
  
  // DOM 조립
  productPanel.appendChild(ProductSelect());
  productPanel.appendChild(AddToCartButton());
  leftColumn.appendChild(productPanel);
  leftColumn.appendChild(CartContainer());
  
  root.appendChild(header);
  root.appendChild(mainGrid);
}

C. 이벤트 관리 (core/)

events.js - 중앙화된 이벤트 핸들러

export function initializeEvents() {
  setupAddButtonHandler();
  setupCartClickHandler();
  setupHelpManualEvents();
}

function setupAddButtonHandler() {
  getAddButtonElement().addEventListener('click', () => {
    const selItem = getProductSelectElement().value;
    const itemToAdd = getProductById(selItem);

    if (!itemToAdd || itemToAdd.stock <= 0) return;

    // 비즈니스 로직 실행
    if (existingItem) {
      updateExistingItemQuantity(existingItem, itemToAdd);
    } else {
      addNewItemToCart(itemToAdd);
    }

    CartController.updateCartDisplay();
  });
}

D. 컴포넌트 (components/)

UI 컴포넌트를 함수로 생성

// Header.js
export function Header() {
  const header = document.createElement('header');
  header.className = 'mb-8';
  header.innerHTML = `
    <h1>🛒 Hanghae Online Store</h1>
    <div class="text-5xl">Shopping Cart</div>
    <p id="item-count">🛍️ 0 items in cart</p>
  `;
  return header;
}

4. 도메인별 기능 분석 (features/) - Cart 도메인

Cart 도메인 구조

features/cart/
├── CartController.js       # 장바구니 오케스트레이션
├── CartRenderer.js         # UI 렌더링 담당
├── CartService.js          # 비즈니스 로직
└── components/             # UI 컴포넌트 생성 함수
    ├── CartContainer.js
    ├── CartItem.js
    └── OrderSummary.js

A. Model 폴더 상세 분석

┌──────────────────────────────────────────────────────────────┐
│                    Cart Domain Structure                     │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐       │
│  │Controller.js│───▶│ Service.js  │───▶│Renderer.js  │       │
│  │             │    │             │    │             │       │
│  │ • update    │    │ • calculate │    │ • render    │       │
│  │   Display   │    │   Cart      │    │   Items     │       │
│  │ • update    │    │ • extract   │    │ • render    │       │
│  │   Prices    │    │   Data      │    │   Summary   │       │
│  │ • render    │    │ • isEmpty   │    │ • render    │       │
│  │   Items     │    │             │    │   Points    │       │
│  └─────────────┘    └─────────────┘    └─────────────┘       │
│                              │                               │
│                              │                               │
│                              ▼                               │
│                  ┌─────────────────────────┐                 │
│                  │    Business State       │                 │
│                  │                         │                 │
│                  │ • products[]            │                 │
│                  │ • totalAmount           │                 │
│                  │ • itemCount             │                 │
│                  │ • bonusPoints           │                 │
│                  └─────────────────────────┘                 │
│                                                              │
└──────────────────────────────────────────────────────────────┘

CartController.js - 장바구니 전체 오케스트레이션

export function updateCartDisplay() {
  const cartItems = getCartDisplayElement().children;

  // 1. 상태 초기화
  setTotalAmount(0);
  setItemCount(0);

  // 2. DOM에서 장바구니 데이터 추출
  const cartData = CartService.extractCartData(cartItems);

  // 3. 장바구니 전체 계산
  const cartResult = CartService.calculateCart(cartData, getProductById);

  // 4. 상태 업데이트
  setTotalAmount(cartResult.finalAmount);
  setItemCount(cartResult.totalQuantity);

  // 5. UI 렌더링
  CartRenderer.renderOrderSummary(cartResult);
  CartRenderer.renderCartCount(cartResult.totalQuantity);
  
  // 6. 포인트 계산 및 표시
  updateBonusPoints();
}

B. Service 레이어 분석

CartService.js - 순수 함수로 비즈니스 로직 처리

export function calculateCartSummary(cartData, getProductById) {
  let subtotal = 0;
  let totalQuantity = 0;
  let discountedTotal = 0;
  const individualDiscountInfo = [];

  for (const item of cartData) {
    const product = getProductById(item.id);
    if (!product) continue;

    const quantity = item.quantity;
    const itemSubtotal = product.price * quantity;

    totalQuantity += quantity;
    subtotal += itemSubtotal;

    // 개별 상품 할인 계산
    const discountRate = getProductDiscount(product.id, quantity);
    if (discountRate > 0) {
      individualDiscountInfo.push({
        name: product.name,
        discountPercent: discountRate * 100,
      });
    }

    discountedTotal += itemSubtotal * (1 - discountRate);
  }

  return { subtotal, totalQuantity, discountedTotal, individualDiscountInfo };
}

C. Renderer 레이어 분석

CartRenderer.js - DOM 조작을 통한 UI 렌더링

export function renderCartItems(cartData) {
  const cartDisplay = getCartDisplayElement();
  if (!cartDisplay) return;

  if (!cartData || cartData.length === 0) {
    cartDisplay.innerHTML = '';
    return;
  }

  cartDisplay.innerHTML = cartData
    .map((item) => {
      const product = getProductById(item.id);
      return CartItem(item, product);
    })
    .join('');
}

D. Components 폴더 분석

CartItem.js - 장바구니 아이템 HTML 생성

export function CartItem(item, product) {
  if (!product) return '';

  // 할인 아이콘 결정
  const discountIcon = product.isOnSale && product.isSuggestedSale
    ? '⚡💝' : product.isOnSale ? '⚡' : product.isSuggestedSale ? '💝' : '';

  // 가격 표시 결정  
  const priceDisplay = product.isOnSale || product.isSuggestedSale
    ? `<span class="line-through">₩${product.originalPrice.toLocaleString()}</span> 
       <span class="${getDiscountColor(product)}">₩${product.price.toLocaleString()}</span>`
    : `${product.price.toLocaleString()}`;

  return `
    <div id="${product.id}" class="grid grid-cols-[80px_1fr_auto] gap-5 py-5">
      <div class="w-20 h-20 bg-gradient-black"></div>
      <div>
        <h3>${discountIcon}${product.name}</h3>
        <p class="text-xs">${priceDisplay}</p>
        <!-- 수량 조절 버튼들... -->
      </div>
      <div class="text-right">
        <div class="text-lg">${priceDisplay}</div>
        <a class="remove-item" data-product-id="${product.id}">Remove</a>
      </div>
    </div>
  `;
}

5. 아키텍처 설계 원칙

A. 도메인 분리

  • Cart: 장바구니 상태와 계산 로직
  • Product: 상품 정보와 재고 관리
  • Promotion: 번개세일, 추천할인 타이머
  • Order: 할인율 계산, 포인트 적립 등

B. 레이어 분리 (MVC 패턴)

View (DOM Elements) ← 렌더링 ← Renderer
     ↑                            ↑
   이벤트                         결과
     ↓                            ↓
Controller (오케스트레이션) → Service (비즈니스 로직)
     ↓                            ↓
Business State (전역 상태) ← 상태 변경 ←

C. 데이터 흐름

  1. 사용자 이벤트 발생 → 이벤트 핸들러 실행
  2. Controller 메서드 호출 → 비즈니스 로직 오케스트레이션
  3. Service 함수 실행 → 순수 함수로 계산 처리
  4. Business State 업데이트 → 전역 상태 변경
  5. Renderer 함수 호출 → DOM 직접 조작으로 UI 업데이트
📍 심화 과제

1. 전체 아키텍처 개요

src/advanced/
├── App.tsx                 
├── main.tsx               
├── main.advanced.js      
├── __tests__/            
├── features/              # 도메인별 기능 모듈
│   ├── cart/             
│   ├── order/            
│   ├── product/       
│   └── promotion/     
└── shared/               # 공통 모듈
    ├── components/  
    ├── constants/      
    ├── core/            
    ├── hooks/          
    ├── services/      
    ├── types/          
    └── utils/          

2. 메인 컴포넌트 분석

App.tsx

function App() {
  const { state, actions, getProductById } = useShoppingCart();

  return (
    <div className="flex flex-col h-full">
      <Header itemCount={state.itemCount} />
      <MainGrid>
        <LeftColumn>
          <ProductPanel>...</ProductPanel>
          <CartContainer {...cartProps} />
        </LeftColumn>
        <OrderSummary {...orderProps} />
      </MainGrid>
      <HelpToggleButton />
    </div>
  );
}
  • 단일 커스텀 훅 사용: useShoppingCart()로 모든 상태와 액션을 관리
  • 컴포넌트 조합: 작은 컴포넌트들을 조합해서 UI 구성
  • Props Drilling 최소화: 필요한 props만 전달

3. 공통 모듈 분석 (shared/)

A. 상태 관리 핵심 (core/)

combinedReducer.ts - 모든 도메인의 리듀서를 하나로 결합

export interface RootState {
  cart: CartState;
  product: ProductState;
  promotion: PromotionState;
}

export type RootAction = CartAction | ProductAction | PromotionAction;

export function combinedReducer(state: RootState, action: RootAction): RootState {
  return {
    cart: cartReducer(state.cart, action as CartAction),
    product: productReducer(state.product, action as ProductAction),
    promotion: promotionReducer(state.promotion, action as PromotionAction),
  };
}

B. 통합 훅 (hooks/)

useShoppingCart.ts - 모든 도메인을 하나로 묶는 최상위 훅

export function useShoppingCart() {
  const [rootState, dispatch] = useReducer(combinedReducer, initialRootState);

  // 각 도메인별 hook 사용
  const cart = useCart(rootState, dispatch);
  const products = useProducts(rootState, dispatch);
  const promotion = usePromotion(rootState, dispatch);

  // 계산된 값들을 메모이제이션
  const calculatedTotals = useMemo(() => calculateTotals(rootState), [rootState]);

  // 통합된 상태와 액션 반환
  return { 
    state: { ...cartState, ...productState, ...calculatedTotals }, 
    actions: { ...cartActions, ...productActions },
    getProductById: products.getProductById 
  };
}

C. 서비스 레이어

stockService.ts - 재고 검증과 에러 처리:

export class StockValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'StockValidationError';
  }
}

export const stockService = {
  validateAddToCart: (product: Product | undefined): void => {
    if (!product) throw new StockValidationError('상품을 찾을 수 없습니다.');
    if (product.stock === 0) throw new StockValidationError('재고가 부족합니다.');
  },
  // ... 기타 검증 메서드들
};

calculationService.ts - 전체 주문 계산을 담당:

export function calculateTotals(state: RootState): CalculatedTotals {
  const cartItems = cartSelectors.getItems(state.cart);
  
  if (cartItems.length === 0) {
    return { itemCount: 0, totalAmount: 0, /* ... */ };
  }

  const cartResult = calculateCart(cartItems, getProductById);
  const pointsResult = calculateTotalPoints(/* ... */);

  return {
    itemCount: cartResult.totalQuantity,
    totalAmount: cartResult.finalAmount,
    bonusPoints: pointsResult.finalPoints,
    // ... 기타 계산 결과
  };
}

D. 타입 시스템 (types/)

타입들을 도메인별로 분리하고 중앙에서 관리

types/
├── entities.ts      # Product, CartItem 등 기본 엔티티
├── calculations.ts  # 계산 관련 타입들
├── hooks.ts         # 훅들의 반환 타입
├── legacy.ts        # 기존 코드 호환용
└── index.ts      

기본 엔티티 예시:

// entities.ts
export interface Product {
  id: string;
  name: string;
  price: number;
  originalPrice: number;
  stock: number;
  isOnSale: boolean;
  isSuggestedSale: boolean;
}

export interface CartItem {
  id: string;
  quantity: number;
}

4. 도메인별 기능 분석 (features/) - Cart 도메인 중심

Cart 도메인 구조

features/cart/
├── components/          # UI 컴포넌트
│   ├── CartContainer.tsx
│   ├── CartItem.tsx
│   ├── OrderSummary.tsx
│   └── index.ts
├── hooks/
│   └── useCart.ts      # 장바구니 비즈니스 로직
├── model/              # 상태 관리 
│   ├── actions.ts      # 액션 정의
│   ├── reducer.ts      # 리듀서
│   └── selectors.ts    # 상태 선택자
└── services/
    └── CartService.ts  # 장바구니 계산 로직

A. Model 폴더 상세 분석

┌──────────────────────────────────────────────────────────────┐
│                    Cart Domain Model                         │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐       │
│  │ actions.ts  │───▶│ reducer.ts  │───▶│selectors.ts │       │
│  │             │    │             │    │             │       │
│  │ • addToCart │    │ • ADD_TO_   │    │ • getItems  │       │
│  │ • updateQty │    │   CART      │    │ • getCount  │       │
│  │ • remove    │    │ • UPDATE_   │    │ • getById   │       │
│  │ • clear     │    │   QUANTITY  │    │ • isEmpty   │       │
│  │             │    │ • REMOVE    │    │ • hasItem   │       │
│  │             │    │ • CLEAR     │    │             │       │
│  └─────────────┘    └─────────────┘    └─────────────┘       │
│                              │                               │
│                              │                               │
│                              ▼                               │
│                     ┌─────────────┐                          │
│                     │ CartState   │                          │
│                     │             │                          │
│                     │ {           │                          │
│                     │   items: [] │                          │
│                     │ }           │                          │
│                     └─────────────┘                          │
│                                                              │
└──────────────────────────────────────────────────────────────┘

actions.ts - 장바구니에서 발생할 수 있는 모든 액션들

export type CartAction =
  | { type: 'cart/ADD_TO_CART'; payload: string }
  | { type: 'cart/UPDATE_QUANTITY'; payload: { id: string; quantity: number } }
  | { type: 'cart/REMOVE_FROM_CART'; payload: string }
  | { type: 'cart/CLEAR_CART' };

export const cartActions = {
  addToCart: (productId: string): CartAction => ({
    type: 'cart/ADD_TO_CART',
    payload: productId,
  }),
  // ... 기타 액션 생성자들
};

reducer.ts - 순수 함수로 상태 변화를 관리

export interface CartState {
  items: CartItem[];
}

export function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case 'cart/ADD_TO_CART': {
      const productId = action.payload;
      const existingItemIndex = state.items.findIndex(
        (item) => item.id === productId
      );

      if (existingItemIndex >= 0) {
        // 기존 아이템 수량 증가
        const newItems = state.items.map((item, index) =>
          index === existingItemIndex
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
        return { ...state, items: newItems };
      } else {
        // 새 아이템 추가
        return { ...state, items: [...state.items, { id: productId, quantity: 1 }] };
      }
    }
    // ... 기타 케이스들
  }
}

selectors.ts - 상태에서 필요한 데이터를 효율적으로 추출

export const cartSelectors = {
  getItems: (state: CartState): CartItem[] => state.items,
  
  getItemCount: (state: CartState): number =>
    state.items.reduce((total, item) => total + item.quantity, 0),
  
  getItemById: (state: CartState, id: string): CartItem | undefined =>
    state.items.find((item) => item.id === id),
  
  isEmpty: (state: CartState): boolean => state.items.length === 0,
  
  hasItem: (state: CartState, id: string): boolean =>
    state.items.some((item) => item.id === id),
};

B. Hooks 폴더 분석

useCart.ts - 장바구니 비즈니스 로직

export function useCart(state: RootState, dispatch: (action: RootAction) => void) {
  const addToCart = useCallback(
    (productId: string) => {
      try {
        const product = productSelectors.getProductById(state.product, productId);
        stockService.validateAddToCart(product);

        // 트랜잭션처럼 두 액션을 함께 실행
        dispatch(cartActions.addToCart(productId));
        dispatch(productActions.updateStock(productId, -1));
      } catch (error) {
        if (error instanceof StockValidationError) {
          notificationService.showError(error.message);
        }
      }
    },
    [state.product, dispatch]
  );

  // updateQuantity, removeFromCart 등 기타 메서드들...

  return {
    cartItems: cartSelectors.getItems(state.cart),
    itemCount: cartSelectors.getItemCount(state.cart),
    isEmpty: cartSelectors.isEmpty(state.cart),
    addToCart,
    updateQuantity,
    removeFromCart,
  };
}

C. Services 폴더 분석

CartService.ts - 순수 함수로 장바구니 계산 담당

export function calculateCartSummary(cartItems: CartItem[], getProductById: Function): CartSummary {
  let subtotal = 0;
  let totalQuantity = 0;
  const individualDiscountInfo: DiscountInfo[] = [];

  for (const item of cartItems) {
    const product = getProductById(item.id);
    if (!product) continue;

    const itemSubtotal = product.price * item.quantity;
    totalQuantity += item.quantity;
    subtotal += itemSubtotal;

    // 개별 할인 계산 로직...
  }

  return { subtotal, totalQuantity, /* ... */ };
}

export function calculateCart(cartItems: CartItem[], getProductById: Function): CartCalculation {
  const cartSummary = calculateCartSummary(cartItems, getProductById);
  const bulkDiscounts = applyBulkDiscounts(/* ... */);
  
  return { /* 최종 계산 결과 */ };
}

D. Components 폴더 분석

CartContainer.tsx - 장바구니 전체를 렌더링하는 컨테이너

export function CartContainer({ cartItems, getProductById, onUpdateQuantity, onRemoveItem }) {
  if (cartItems.length === 0) {
    return (
      <div id="cart-items">
        <p className="text-gray-500 text-center py-8">🛍️ 0 items in cart</p>
      </div>
    );
  }

  return (
    <div id="cart-items">
      {cartItems.map((item) => {
        const product = getProductById(item.id);
        if (!product) return null;

        return <CartItemComponent key={item.id} /* props */ />;
      })}
    </div>
  );
}

5. 아키텍처 설계 원칙

A. 도메인 분리

  • Cart: 장바구니 상태와 아이템 관리
  • Product: 상품 정보와 재고 관리
  • Promotion: 번개세일, 추천할인 등 프로모션
  • Order: 할인율 계산, 포인트 적립 등

B. 레이어 분리

UI Components (렌더링만 담당)
Hooks (비즈니스 로직)
Services (도메인 로직)
State Management (순수한 상태 관리)

C. 데이터 흐름

  1. UI에서 액션 발생 → Hook 호출
  2. Hook에서 비즈니스 로직 처리 → Service 호출 + Validation
  3. Service에서 도메인 로직 실행 → 필요시 여러 액션 디스패치
  4. Reducer에서 상태 변경 → 새로운 상태 반환
  5. Selector로 필요한 데이터 추출 → UI 리렌더링

실제 개발하면서 가장 고민했던 부분들

1. 레거시 코드 점진적 개선

270줄짜리 거대한 main() 함수를 보고 어디서부터 손대야 할지 막막했습니다. 테스트코드를 참고해 단계별로 접근했어요:

  • 1차: 전역변수들을 appState 객체로 모으기
  • 2차: 큰 함수를 작은 함수들로 쪼개기
  • 3차: 비슷한 기능끼리 폴더로 묶기

매번 리팩토링 후 테스트가 모두 통과하는지 확인하는 게 필수였어요.. 🥲

2. React 마이그레이션을 고려한 설계

기본과제 진행 중에도 "이 구조가 React로 전환할 때 자연스러울까?"를 계속 고민했습니다.

  • CartController.updateCartDisplay()useCart() 훅으로 바뀔 거고
  • CartRenderer.renderOrderSummary()<OrderSummary /> 컴포넌트가 될 거고
  • CartService.calculateCart() → 그대로 비즈니스 로직으로 쓸 수 있고

그래서 기본과제에서도 Controller-Service-Renderer 패턴으로 나누어서 React 전환이 쉽도록 했습니다.

3. 상태관리 설계

기존 코드의 10개 넘는 전역변수를 어떻게 정리할지가 큰 과제였습니다. appState 객체로 통합하고 접근자 함수를 만든 후, 심화과제에서 useReducer로 자연스럽게 전환할 수 있었습니다. 기본과제 단계에서 React 전환을 염두에 둔 구조 설계가 심화과제 진행에 큰 도움이 되었습니다.

과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?

  • 타입스크립트를 제대로 활용하지 못했어요.
  • 심화 과제에서 컴포넌트를 더 작고 명확하게 나누지 못한 점이 아쉬워요.

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)

1. 도메인 중심 설계 + MVC 패턴의 적절성

src/basic/domains/
├── cart/
│   ├── CartController.js    # 오케스트레이션
│   ├── CartService.js       # 비즈니스 로직  
│   └── CartRenderer.js      # UI 렌더링

Controller-Service-Renderer 3계층으로 나눈 구조가 적절적절했는지, 그리고 React로 마이그레이션할 때 이런 레이어 분리가 도움이 되었는지 궁금합니다!

2. React 상태 관리

// useShoppingCart.ts - 모든 도메인을 하나로 묶는 최상위 훅
export function useShoppingCart() {
  const [rootState, dispatch] = useReducer(combinedReducer, initialRootState);
  
  const cart = useCart(rootState, dispatch);
  const products = useProducts(rootState, dispatch);
  const promotion = usePromotion(rootState, dispatch);
  // ...
}

하나의 combinedReducer로 모든 상태를 관리하는 방식이 적절한지 궁금합니다. 장바구니만 변경되어도 전체 상태가 업데이트되는데, Context 분리나 다른 상태 관리 패턴을 고려해야 할까요?

3. 전체적인 개선 방향

심화과제 React+TypeScript로 마이그레이션하면서 useReducer로 재설계했는데, 현재 구조가 React다운 설계인지 아니면 더 React스러운 패턴으로 개선할 부분이 있는지 피드백 받고 싶습니다! 🤔 그리고 Javascript → React 마이그레이션 과정에서 놓친 React 베스트 프랙티스가 있다면 알고 싶습니다!

과제 피드백

지수님 고생하셨습니다. 작성하신 문서를 읽는데도 시간이 굉장히 오래걸렸는데요. 꼼꼼하게 잘 작성해주신걸 보니 과제를 어떤 방향으로 진행할 수 있었는지 명확하게 이해할 수 있었습니다. 시각화한 다이어그램도 좋았어요 :+1 과제는 전반적으로 매우 잘 작성해주셨어요! 크게 피드백 드릴만한 내용이 없는 것 같습니다.

궁금하셨던 내용 답변드려보고 마무리 해볼게요.

도메인 중심 설계 + MVC 패턴의 적절성

좋은 접근법이였어요! 비즈니스 로직들을 미리 나눠둔 덕분에 리액트로 마이그레이션 할 때 별도 분리를 진행하면서 코드를 재작성하지 않아도 되었던것으로 생각이 됩니다. :+1

  1. React 상태 관리

단일 진실 공급원 관점에서는 한 곳에서 관리를 하면 일관성이 보장된다는 점도 있고 상태 변화를 한 곳에서 추적할 수 있기 때문에 함께 업데이트 되어야 하는 값들이 자연스럽게 처리될 수 있긴 한 것 같아요. 지금의 단계에서는 상품의 양이나 성능 이슈가 발생할 만큼은 걱정하지 않아도 괜찮은 수준인 것 같고, 최적화는 문제가 발생하는 그 시점에 고민을 하면 되기 때문에 갠적으로는 분리를 하면 오히려 복잡도만 높아질수도 있을 것 같네요!

전체적인 개선 방향

걱정하실 필요없이 잘 작성해주셨어요. useReducer같은 것들을 사용해서 명확하게 상태를 관리해주시고 관심사의 분리나 훅을 구성하는 부분들도 너무 잘 해주셨습니다. 더 나아간다면 앞선 주차때 했던 메모이제이션이나 에러 바운더리, suspense, 합성 패턴 같은것들이나... 포탈같은 것들을 활용해보는것?들 정도이지 않을까 싶네요!

이번 주 과제 너무 잘해주셨고 다음주도 이번주처럼 잘 하시길 바랍니다!