Skip to content

[8팀 현지수] Chapter 2-1. 클린코드와 리팩토링#46

Open
hyunzsu wants to merge 58 commits into
hanghae-plus:mainfrom
hyunzsu:main
Open

[8팀 현지수] Chapter 2-1. 클린코드와 리팩토링#46
hyunzsu wants to merge 58 commits into
hanghae-plus:mainfrom
hyunzsu:main

Conversation

@hyunzsu
Copy link
Copy Markdown

@hyunzsu hyunzsu commented Jul 30, 2025

과제 체크포인트

기본과제

  • 코드가 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 베스트 프랙티스가 있다면 알고 싶습니다!

hyunzsu added 30 commits July 28, 2025 15:03
- var → let/const 변경
- 사용되지 않는 변수 제거
- 빈 블록문 → TODO 주석 변경
- 변수명 중복 해결
- 괄호 표기법 → 점 표기법 변경
- 매직 넘버 상수화
- 불필요한 IIFE 및 중복 로직 제거
@ckdwns9121
Copy link
Copy Markdown
Member

지수님 고생하셨어요..! 코드가 딱 알아보기 좋고 잘 분리되어있게 추상화가 잘되어있네요 보고 배웁니다 !!

@k-sang-soo
Copy link
Copy Markdown

BP 축하드립니다 지수님👏

@heojungseok
Copy link
Copy Markdown

???: 봐봐 할 수 있잖아 BP

Copy link
Copy Markdown

@unseoJang unseoJang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한번 썻다 지워서...그냥 여기에다가 전체 코멘트 남길게요
미리 다음 과제를 해놓은 느낌이 커요
FSD를 통한 추상화도 잘햊쉬고 분기점도 저보다 훨씬 잘 나눠 놓으신것같아요오
저번에 처음 봣을떄보다도 훨씬더 세부적으로 작업을 해놓으셧네요
1년차때 좋은 선임 만나보면 이정도 까지도 작업을 할수있다는게 느껴지네요 BP는 역시 달라요

다음과제도 화이팅입니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants