티스토리 뷰

항해 플러스 프론트엔드 5기

이번 주차 목표 (디자인 패턴과 함수형 프로그래밍)

(1) 값 함수 액션을 구분할 수 있다.

 

 

1. 문제 (과제, 프로젝트를 진행하면서 부딪혔던 기술적인 문제)

- ProductManager 추상화

현재 새 상품 추가는 ProductForm 컴포넌트로 분리했지만, 기존 상품 수정 영역은 핸들러 함수가 많아져 UI와 로직을 분리하지 못했다.
특히 setEditingProduct를 직접 다루는 handleProductNameUpdate, handlePriceUpdate, handleStockUpdate처럼 필드별로 상태를 갱신하는 로직이 반복되면서 코드가 점점 길어지고 중복되는 느낌이 들었다.

 

- 액션과 순수함수 분리에 대한 고민

로직을 어디까지 hook 안에 두고, 어떤 계산을 utils 또는 models로 분리해야 할지 기준을 세우는 것이 쉽지 않았다. 특히 models와 hooks의 역할 차이는 아직 명확히 감이 오지 않았다.

 

 

2. 시도 및 해결

1. 폴더 구조 및 모듈화

refactoring/
├── components/
│   ├── admin/        # 관리자 기능 관련 컴포넌트
│   ├── cart/         # 장바구니 관련 컴포넌트
│   ├── layout/       # 공통 레이아웃 컴포넌트 (CardBox, Title 등)
│   └── shared/       # 재사용 가능한 UI 컴포넌트 (Input)
├── data/             # 정적인 데이터 또는 mock 데이터
├── hooks/            # 커스텀 훅
├── models/           # 도메인 중심의 계산 로직과 모델
├── pages/            # 라우트 단위의 페이지 컴포넌트
├── utils/            # 유틸 함수 모음
├── types/            # 타입 모음
├── App.tsx           # 앱 루트 컴포넌트
├── main.tsx          # 진입 파일

 

이번 프로젝트는 규모가 크지 않다는 점을 고려해 기능별로 폴더를 구분하는 방향으로 구조를 잡았다.

  • 기능별 컴포넌트 분리
  • 공통 UI는 shared/, 라우트 단위는 pages/
  • 재사용 가능한 로직은 hooks/, 도메인 중심 계산 로직은 models/에 정리하는 데 중점을 뒀다.

 

2. 유틸 함수 분리 구조

현재 utils/ 폴더 아래 유틸 함수들을 기능별로 나누어 관리하고 있다.

utils/
├── coupon.ts        // 쿠폰 관련 유틸
├── discount.ts      // 할인 계산 유틸
├── format.ts        // 포맷 관련 유틸
├── product.ts       // 상품 관련 유틸

처음에는 "어떤 계산을 하느냐"보다 "어떤 도메인(의존성)을 다루느냐"를 기준으로 파일 구조를 정리했다.

지금은 파일이 적지만, 나중에 유틸 함수가 많아질 경우를 대비해 utils/index.ts를 만들어 중앙 관리하는 방향으로 확장할 계획이다.

 

 

3. discountType 정의 개선

처음에는 discountType을 "amount" | "percentage"처럼 단순한 문자열 유니언 타입으로 선언 되어 있었다.
coupon.discountType === 'amount'처럼 하드코딩된 문자열 비교가 반복되고, 오타에 취약한 점이 신경 쓰였다.

그래서 다음과 같이 상수 객체와 타입을 함께 정의하는 방식으로 수정했다.

export const DISCOUNT_TYPE = {
  AMOUNT: 'amount',
  PERCENTAGE: 'percentage',
} as const;

export type DiscountType = (typeof DISCOUNT_TYPE)[keyof typeof DISCOUNT_TYPE];

이렇게 변경하고 나니,

  • 비교할 때 DISCOUNT_TYPE.AMOUNT처럼 사용할 수 있어 안정적이고,
  • 할인 타입이 추가될 때도 DISCOUNT_TYPE에만 수정하면 되어 확장성이 높아졌다.

 

 

3. 개선해야 할 것

- ProductManager 추가 개선

  • 핸들러 로직이 많아지다 보니, UI 정리에 충분히 집중하지 못한 점이 아쉬웠다.
    다음에는 핸들러와 상태 관리를 좀 더 구조적으로 나누고, UI 코드와의 분리를 고려한 설계를 개선해보고 싶다.

- useLocalStorage 구현

현재 useLocalStorageState 훅은 다음과 같이 작성했다.

export function useLocalStorageState<T>(key: string, defaultValue: T) {
  const [state, setState] = useState<T>(() => {
    const stored = localStorage.getItem(key);

    // 저장하기
    if (stored) {
      return JSON.parse(stored);
    }

    // 없으면 defaultValue를 사용하고 localStorage에 저장하기
    localStorage.setItem(key, JSON.stringify(defaultValue));
    return defaultValue;
  });

  // state가 변경될 때마다 localStorage에 저장하기
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(state));
  }, [key, state]);

  return [state, setState] as const;

그런데 코드 리뷰에서 "useEffect를 제거해도 동작하는 것 아닌가?"라는 피드백을 받았다.
이 부분은 깊게 고민해보지 않고 넘어갔었는데, 실제로 useEffect 없이도 정상 동작하는지 검토해볼 필요가 있을 것 같다.
특히 "초기 렌더"와 "state 변경 시 동기화"가 정확히 구분되어야 하는 부분이라, 이번 기회에 localStorage와 상태 동기화 타이밍에 대한 이해를 좀 더 깊게 다져보고 싶다.

 

 

 

4. 알게된 것

- MSW(Mock Service Worker)를 간단하게라도 적용해보면서, 실제 네트워크 요청 없이 브라우저 레벨에서 요청을 가로채 mock 응답을 반환하는 방식을 배웠다.

예를 들어, 아래와 같이 간단한 핸들러를 통해 상품 목록을 불러올 수 있다.

// mocks/handlers.ts
import { rest } from 'msw';

let mockProducts = [
  { id: '1', name: '상품 A', price: 10000 },
  { id: '2', name: '상품 B', price: 20000 },
];

export const handlers = [
  // 상품 목록 불러오기
  rest.get('/api/products', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(mockProducts));
  }),
];

MSW를 이용하면, 서버가 준비되지 않았거나 외부 API 의존 없이 프론트엔드 개발과 테스트를 훨씬 수월하게 진행할 수 있다는 점을 체감할 수 있었다.

 

- 이번 과제를 진행하면서 단순히 기능이 되는 코드에 그치지 않고,

  • utils 분리,
  • 계산 함수의 순수화,
  • 도메인별 책임 분리

를 기준으로 코드를 구성하려고 노력했다.
특히, "각 계층이 어떤 책임을 가져야 하는가?" 를 더 깊이 고민할 수 있었던 점이 큰 수확이었다.

 

 

 

---

Keep : 현재 만족하고 계속 유지할 부분

구현을 못해도 공부해보기

 

이번 주차 심화 과제에는 useLocalStorage 훅 구현, MSW(Mock Service Worker) 사용, 회원 등급별 할인 기능 등의 예시 요구사항이 있었다. 나는 그중 상대적으로 만만했던 useLocalStorage 훅을 선택해 구현했다.

사실 MSW도 사용해보고 싶었지만, 시간 부족으로 직접 적용하지는 못했다. 대신 아쉬운 마음에 MSW의 동작 방식과 주요 특징을 간단히 공부했다. 학습메이트님도 "꼭 한 번 사용해보면 좋을 것 같다"고 추천해주신 만큼, 다음 과제에 대비해 준비해두는 것이 좋겠다고 생각했다.

비록 이번에는 적용까지 이어지지 못했지만, 그냥 지나치지 않고 공부한 덕분에 다음에 MSW를 사용할 기회가 온다면 더 자연스럽게 활용할 수 있을 것 같다는 자신감이 생겼다.

Problem : 개선이 필요하다고 생각하는 문제점

시간 관리 부족

 

현재도 일을 병행하고 있어서, 평소에는 구현 가능한 범위를 예상하고 일정 산정을 꾸준히 해왔다. 하지만 항해를 진행하면서는 예상치 못한 변칙적인 일들이 자주 발생해 계획대로 진행하기가 쉽지 않았다.

처음에는 "화요일까지 기본 과제 완료 → 수요일에 심화 과제 진행 → 목요일에 리팩토링 및 PR 작성" 같은 주간 계획을 세웠지만, 생각보다 일정이 밀리는 경우가 많았다.

벌써 5주차에 접어들었지만, 아직도 내가 기대했던 만큼 개발을 완성하지 못하는 경우가 많다는 점을 아쉽게 느끼고 있다.

Try : 문제점을 해결하기 위해 시도해야 할 것

구현 예상 시간을 체계적으로 잡아보기

 

사실 토요일이나 일요일부터 과제를 시작하면 심화 과제도 훨씬 수월하게 진행할 수 있다. 하지만 주말에 시간을 내서 책상에 오래 앉아 있는 게 생각만큼 쉽지 않다. 결국 일요일 저녁이 되어서야 부랴부랴 시작하는 패턴이 반복되고 있다.

1~2주차에 비해 점점 긴장이 풀리고, 일정 관리에 안일해진 것도 체감하고 있다. 그래서 앞으로는 "월요일까지 기본 과제 완료"를 목표로 삼고, 토요일까지는 푹 쉬더라도 일요일에는 반드시 과제를 시작하는 루틴을 만들어야겠다고 다짐했다.

벌써 6주차, 시간 관리에 느슨해진 내 모습... 정상인가요? 😂