Skip to content

Create Week9 Mission 0, 1, 2, 3#123

Open
hyeok02 wants to merge 1 commit intomainfrom
hyeok02/week09
Open

Create Week9 Mission 0, 1, 2, 3#123
hyeok02 wants to merge 1 commit intomainfrom
hyeok02/week09

Conversation

@hyeok02
Copy link
Contributor

@hyeok02 hyeok02 commented Dec 15, 2025

📝 미션 번호

9주차 Misson 0,1,2,3

📋 구현 사항

  • Reducer 실습
  • Redux Toolkit 활용 UMC Play List 제작
  • Modal Slice 활용하여, 모달 기능 추가
  • Redux Toolkit으로 만든 UMC Play List를 Zustand로 리팩토링

📎 스크린샷

2025-12-15.175308.mp4

✅ 체크리스트

  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?
  • 불필요한 주석이 제거되었나요?
  • 코드 스타일이 일관적인가요?

🤔 질문 사항

@hyeok02 hyeok02 requested a review from woojo230 December 15, 2025 09:04
@hyeok02 hyeok02 self-assigned this Dec 15, 2025
@github-actions
Copy link

🤖 Gemini 코드리뷰 결과

안녕하세요, 시니어 프론트엔드 개발자로서 제공해주신 Pull Request의 모든 파트(1/6 ~ 6/6)를 통합하여 면밀히 검토했습니다.

이번 PR에서는 다양한 학습 미션의 흔적과 함께, 핵심 장바구니 기능의 상태 관리가 Redux에서 Zustand로 전환되는 과정을 엿볼 수 있었습니다. 요청하신 기준(특히 src 내부 .ts.tsx 파일에 대한 성능, 타입스크립트, 변수명/주석, 중복/리팩토링, 구체적 개선 제안)에 맞춰 통합 리뷰를 진행합니다.


PR 전반에 대한 초기 인상 및 구조 파악

PR은 크게 다음 세 가지 맥락의 변경사항을 포함하고 있습니다:

  1. 프로젝트 설정 및 의존성 관리: .eslintrc.cjs, package-lock.json, .gitignore, README.md, index.html 등.
  2. useReducer 학습 미션: UseReducerPage.tsx, UseReducerCompany.tsx.
  3. 핵심 장바구니 기능 구현 (Zustand 기반): 기존 Redux 구현에서 Zustand로 마이그레이션된 것으로 보이며, App.tsx, constant/*, page/*, store/store.ts 등의 파일로 구성됩니다.

이 리뷰는 현재 프로젝트의 주 목표가 "핵심 장바구니 기능"이라고 판단하여, Zustand 구현을 중심으로 상세하게 다루고, useReducer 미션 코드는 별도의 섹션으로 분리하여 피드백을 제공합니다.


1. 프로젝트 설정 및 의존성 관리 (package-lock.json, .eslintrc.cjs 등)

  • .eslintrc.cjs: eslint:recommended, @typescript-eslint/recommended, react-hooks/recommended 등 기본적인 린팅 규칙 설정은 좋습니다. 다만, README.md에 언급된 것처럼 plugin:@typescript-eslint/recommended-type-checked 또는 strict-type-checked로 확장하여 타입 기반의 더 엄격한 린팅 규칙을 적용하는 것을 고려해볼 수 있습니다.
  • package-lock.json:
    • @reduxjs/toolkit, @tanstack/react-query, zustand, react-hook-form, tailwindcss, axios, date-fns, lucide-react, react-icons, react-router-dom, react-select, zod 등 최신 프론트엔드 개발에서 많이 사용되는 라이브러리들이 포함되어 있어 현대적인 스택을 사용하고 있음을 보여줍니다.
    • 개선 필요 사항 (매우 중요):
      • eslintinflight 패키지 Deprecated 버전: eslint (8.57.1) 및 inflight (1.0.6) 패키지가 Deprecated 상태입니다. 특히 inflight는 메모리 누수를 야기한다고 명시되어 있으므로, 이 패키지를 사용하는 부분이 있다면 즉시 제거하거나 안정적인 대안으로 교체해야 합니다. eslint도 최신 안정 버전으로 업그레이드하는 것이 좋습니다.
      • globrimraf Deprecated: 이 두 패키지 역시 Deprecated로 표시되어 있으니, 호환 가능한 최신 버전으로 업데이트하여 안정성과 보안을 확보해야 합니다.
      • react-router Major 버전 업데이트 (v7): react-routerreact-router-dom7.5.0으로 업데이트되었습니다. v6에서 v7로 넘어갈 때 상당한 변경 사항이 있을 수 있으므로, 라우팅 로직 및 컴포넌트 사용법 등에 대한 애플리케이션 코드 변경이 동반되었는지 확인하고 마이그레이션 가이드를 따르는 것이 중요합니다.
      • date-fns 버전 명확화: date-fns의 버전이 4.1.0으로 기록되어 있는데, 현재 npm의 date-fns 라이브러리는 3.x.x 대가 최신 메이저 버전입니다. 4.x.x 버전이 오타이거나 특정 포크 버전인지 확인이 필요합니다.
      • 보안 감사: 많은 의존성이 업데이트되었으므로 npm audit 또는 yarn audit을 실행하여 잠재적인 보안 취약점을 검토하고 해결하는 것을 권장합니다.

2. 핵심 장바구니 기능 (Zustand 기반) 코드 리뷰 (src 디렉토리)

전반적으로 코드는 깔끔하고 명확하게 작성되었으며, Zustand를 사용하여 상태 관리를 잘 구현했습니다. 타입스크립트도 적절하게 활용하고 있습니다.

파일별 구체적 피드백

  • src/App.tsx
    • 전반: React Router를 사용하여 라우팅을 설정하는 기본적인 구조입니다.
    • 개선점: 향후 여러 페이지가 추가될 경우, Navbar<Outlet>을 포함하는 레이아웃 컴포넌트 내에 배치하여 더 구조적인 레이아웃 관리가 가능합니다. (아래 구체적 개선 제안 6 참고)
  • src/constant/cartitems.ts
    • 전반: 장바구니 아이템 초기 데이터를 정의하고 CartItemType 인터페이스로 타입을 명확히 했습니다.
    • 타입스크립트: CartItemType 인터페이스 정의가 명확하고 좋습니다. id 필드를 readonly로 명시하여 불변성을 강조할 수 있습니다. (아래 구체적 개선 제안 7 참고)
    • 리팩토링: constant 디렉토리보다는 src/data/cartItems.ts 등으로 이동하여 데이터 파일임을 명확히 하는 것이 좋습니다. (아래 구체적 개선 제안 4 참고)
  • src/constant/icons.tsx
    • 전반: SVG 아이콘을 React 컴포넌트로 래핑하여 사용하고 있습니다.
    • 개선점:
      • 접근성 (Accessibility): 아이콘들에 aria-label 또는 title 요소를 추가하여 스크린 리더 사용자에게 목적을 설명해주는 것이 좋습니다. (아래 구체적 개선 제안 2 참고)
      • 일관성: package.jsonlucide-react가 있으므로, 직접 SVG를 만드는 대신 lucide-react 아이콘을 사용하여 일관성을 유지하고 중복 코드를 줄이는 것을 고려해볼 수 있습니다. (아래 구체적 개선 제안 5 참고)
      • 리팩토링: constant 디렉토리보다는 src/components/icons 또는 src/assets/icons 등으로 이동하는 것이 좋습니다. (아래 구체적 개선 제안 4 참고)
  • src/main.tsx
    • 전반: React 앱의 엔트리 포인트로, Zustand Provider 대신 createRootStrictMode를 사용했습니다.
    • 타입스크립트: document.getElementById('root') as HTMLElement와 같이 타입 단언을 사용했으나, root 엘리먼트의 존재 여부를 확인하는 로직을 추가하여 안정성을 높이는 것이 좋습니다. (아래 구체적 개선 제안 8 참고)
  • src/page/cart.tsx
    • 성능/리팩토링: useEffect 내에서 calculateTotals 액션을 호출하여 amounttotal을 재계산하는 방식은 불필요한 리렌더링과 계산을 야기할 수 있습니다. amounttotalcartItems의 파생 상태(derived state)이므로, Zustand 스토어 액션 내부에서 cartItems가 변경될 때마다 자동으로 업데이트되도록 하는 것이 훨씬 효율적입니다. (아래 구체적 개선 제안 1 참고)
    • 성능/최적화: useStore()를 통해 모든 상태를 가져오는 대신, 필요한 상태만 선택적으로 구독하는 selector를 사용하는 것이 좋습니다. (아래 구체적 개선 제안 6 참고)
  • src/page/cartitem.tsx
    • 접근성 (Accessibility): ChevronUp, ChevronDown 아이콘 버튼에 aria-label 속성을 추가하여 스크린 리더 사용자가 버튼의 기능을 이해할 수 있도록 해야 합니다. (아래 구체적 개선 제안 2 참고)
    • 타입스크립트: React.FC 타입 대신 props 타입을 직접 명시하는 것을 권장합니다. (아래 구체적 개선 제안 5 참고)
  • src/page/modal.tsx
    • 접근성 (Accessibility): 모달 컴포넌트는 키보드 포커스 관리 (포커스 트랩, 복원), Esc 키로 닫기, ARIA 속성 (aria-modal, role) 추가 등 접근성을 위한 추가 고려 사항이 필요합니다. (아래 구체적 개선 제안 3 참고)
    • 타입스크립트: React.FC 타입 대신 props 타입을 직접 명시하는 것을 권장합니다. (아래 구체적 개선 제안 5 참고)
  • src/page/navbar.tsx
    • 스타일링 (Tailwind CSS): bg-[#6c63ff]와 같이 하드코딩된 hex 값 대신, tailwind.config.js에 커스텀 색상을 정의하여 사용하는 것이 좋습니다. (아래 구체적 개선 제안 4 참고)
    • 접근성 (Accessibility): 장바구니 아이콘 옆 amountaria-label이나 숨겨진 텍스트를 활용하여 스크린 리더 사용자에게 명확히 전달하는 것을 고려해볼 수 있습니다.
    • 성능/최적화: useStore()를 통해 모든 상태를 가져오는 대신, 필요한 상태만 선택적으로 구독하는 selector를 사용하는 것이 좋습니다. (아래 구체적 개선 제안 6 참고)
    • 타입스크립트: React.FC 타입 대신 props 타입을 직접 명시하는 것을 권장합니다. (아래 구체적 개선 제안 5 참고)
  • src/store/store.ts (Zustand)
    • 전반: Zustand로 장바구니 상태를 관리하며, initialState 주석은 Redux에서 마이그레이션된 맥락을 보여줍니다.
    • 성능/리팩토링:
      • amounttotal 계산 로직이 initialState 설정 부분과 calculateTotals 액션에 중복되어 있습니다.
      • calculateTotals 액션은 cartItems를 변경하는 다른 액션(increase, decrease, removeItem, clearCart)의 사이드 이펙트로 발생하는 파생 상태를 업데이트하는 역할이므로, 각 액션 내에서 amounttotal을 즉시 업데이트하는 것이 효율적입니다.
    • 로직 명확화: decrease 액션에서 amount가 0이 되면 아이템을 제거하는데, removeItem이라는 별도 액션이 있지만 사용되지 않습니다. decrease 로직에서 removeItem을 활용하도록 연결하면 더 명확해집니다.
    • 타입스크립트: StoreState 인터페이스 정의가 명확합니다.

3. useReducer 학습 미션 코드 리뷰 (src/page/UseReducerPage.tsx, src/page/UseReducerCompany.tsx)

useReducer 훅의 기본적인 사용법을 잘 구현했으나, 타입 안전성, 코드 품질, 그리고 몇 가지 개선점을 제안합니다.

  • UseReducerPage.tsx
    • TypeScript: IAction 타입 개선 (Discriminated Unions): payloadINCREASE 액션에서 사용되지 않음에도 불구하고 payload?: number;로 정의되어 있습니다. INCREASE가 항상 1만 증가시킨다면 payload를 제거하고, 만약 증가량을 받아야 한다면 Discriminated Unions를 사용하여 payload를 필수로 명시하는 것이 좋습니다.
    • 코드 품질: console.log 제거: 개발 디버깅용 console.log가 프로덕션 환경에서 성능 저하 및 정보 노출의 원인이 될 수 있습니다. 배포 시 제거하거나 환경 변수로 분기 처리해야 합니다.
    • 리팩토링: 초기 상태 분리: useReducer 훅 내부에 직접 정의된 초기 상태를 컴포넌트 외부에 별도 상수로 선언하여 가독성과 재사용성을 높일 수 있습니다.
  • UseReducerCompany.tsx
    • TypeScript: IAction 타입 개선 (Discriminated Unions 및 Nullish Coalescing): CHANGE_DEPARTMENT 액션의 payload를 필수로 지정하고, reducer 내부에서 payloadundefined가 될 가능성을 명시적으로 처리하거나 타입을 강화하는 것이 좋습니다.
    • CSS/Tailwind: 잘못된 Tailwind CSS 클래스 수정: <p className="text-red-500 font-2xl">에서 font-2xl은 잘못된 클래스입니다. text-2xl로 수정해야 의도한 텍스트 크기가 적용됩니다.
    • 변수명/함수명: 액션 타입 명명 일관성: RESET_TO_ZERORESET처럼 비슷한 의미의 액션 타입에 대한 명명 규칙의 일관성을 유지하는 것이 좋습니다.
    • UX/접근성: Input Placeholder 텍스트 간결화: input 태그 placeholder 텍스트가 너무 길고 기능적인 설명이 포함되어 있습니다. Placeholder는 간결한 힌트를 제공하고, 기능 설명은 별도의 안내문으로 제공하는 것이 사용자 경험에 좋습니다.

구체적 개선 제안 (총 8개)

다음은 위에 언급된 내용을 바탕으로 코드의 유지보수성, 성능, 접근성, 그리고 전반적인 품질을 향상시킬 수 있는 구체적인 제안들입니다.

  1. Zustand 스토어 내 파생 상태(amount, total) 직접 업데이트 및 calculateTotals 제거:

    • src/store/store.ts에서 cartItems를 변경하는 모든 액션(increase, decrease, removeItem, clearCart) 내에서 amounttotal을 즉시 재계산하여 함께 업데이트하도록 변경합니다.
    • 이를 위해 calculateDerivedState 헬퍼 함수를 도입하여 중복을 제거합니다. (아래 제안 3 참고)
    • 이후 src/page/cart.tsx에서 useEffectcalculateTotals() 호출을 제거합니다.
    // src/store/store.ts
    // calculateDerivedState 헬퍼 함수 정의 (create 함수 내부 또는 외부에)
    const calculateDerivedState = (items: CartItemType[]) => ({
      amount: items.reduce((sum, item) => sum + item.amount, 0),
      total: items.reduce((sum, item) => sum + item.price * item.amount, 0),
    });
    
    export const useStore = create<StoreState>((set, get) => ({
      cartItems: cartItemsData,
      ...calculateDerivedState(cartItemsData), // 초기 상태 설정 시 활용
      isModalOpen: false,
    
      increase: (id) => set((state) => {
        const updatedItems = state.cartItems.map(i => i.id === id ? { ...i, amount: i.amount + 1 } : i);
        return { cartItems: updatedItems, ...calculateDerivedState(updatedItems) };
      }),
      decrease: (id) => {
        const itemToDecrease = get().cartItems.find(item => item.id === id);
        if (itemToDecrease && itemToDecrease.amount === 1) {
          get().removeItem(id); // amount가 1일 때 decrease하면 0이 되므로 removeItem 호출
        } else {
          set((state) => {
            const updatedItems = state.cartItems.map(i => i.id === id ? { ...i, amount: i.amount - 1 } : i);
            return { cartItems: updatedItems, ...calculateDerivedState(updatedItems) };
          });
        }
      },
      removeItem: (id) => set((state) => {
        const updatedItems = state.cartItems.filter(i => i.id !== id);
        return { cartItems: updatedItems, ...calculateDerivedState(updatedItems) };
      }),
      clearCart: () => set({ cartItems: [], amount: 0, total: 0 }),
      // calculateTotals 액션 제거
    }));
  2. UI 컴포넌트 접근성(Accessibility) 강화:

    • 아이콘: src/constant/icons.tsxCartIcon, ChevronUp, ChevronDownsrc/page/cartitem.tsx의 버튼에 aria-label 속성을 추가하여 스크린 리더 사용자에게 의미를 전달합니다. (예: <svg aria-label="장바구니 아이콘">, <button aria-label="수량 증가">)
    • 모달: src/page/modal.tsx에 모달이 열릴 때 포커스 트랩 구현, Esc 키로 닫기, ARIA 속성 (aria-modal="true", role="dialog") 추가 등 접근성 모범 사례를 적용합니다.
  3. Zustand useStore 훅에서 선택자(Selector) 사용 최적화:

    • src/page/cart.tsx, src/page/navbar.tsx, src/page/modal.tsx 등에서 useStore()를 호출하고 모든 상태/액션을 구조 분해 할당하는 대신, 컴포넌트가 실제로 필요한 상태만 선택적으로 구독하도록 selector를 활용하여 불필요한 리렌더링을 방지합니다.
    // src/page/cart.tsx 예시
    const cartItems = useStore((state) => state.cartItems);
    const amount = useStore((state) => state.amount);
    const total = useStore((state) => state.total);
    const openModal = useStore((state) => state.openModal);
  4. 타입스크립트: useReducer 액션 타입에 Discriminated Unions 적용:

    • src/page/UseReducerPage.tsxsrc/page/UseReducerCompany.tsxIAction 타입을 Discriminated Unions로 재정의하여 각 액션 타입에 따라 payload의 존재 여부와 타입을 명확히 합니다. 이는 타입 안전성을 크게 향상시킵니다.
    // src/page/UseReducerPage.tsx
    type Action =
      | { type: "INCREASE"; payload?: number } // payload 사용 시 명확히
      | { type: "DECREASE" }
      | { type: "RESET_TO_ZERO" };
    
    // src/page/UseReducerCompany.tsx
    type CompanyAction =
      | { type: "CHANGE_DEPARTMENT"; payload: string } // payload 필수
      | { type: "RESET" };
  5. React.FC 타입 사용 재고 및 표준화:

    • React.FC는 암시적으로 children prop을 포함하는 등의 특징이 있어 최신 React + TypeScript 환경에서는 props 타입을 직접 명시하는 것을 선호하는 경향이 있습니다. 모든 함수형 컴포넌트에서 React.FC를 제거하고 props 인터페이스를 직접 활용하도록 변경합니다.
    // Before: const App: React.FC = () => { ... };
    const App = () => { /* ... */ };
    
    // Before: const CartItem: React.FC<CartItemType> = ({ id, ... }) => { ... };
    const CartItem = ({ id, title, singer, price, img, amount }: CartItemType) => { /* ... */ };
  6. 코드 품질 및 유지보수성 향상:

    • console.log 제거: useReducer 코드 등 개발 디버깅용 console.log 문을 프로덕션 배포 시 제거하거나 환경 변수로 분기 처리합니다.
    • Tailwind CSS 커스텀 색상 사용: src/page/navbar.tsx에서 bg-[#6c63ff]와 같이 하드코딩된 색상 값을 tailwind.config.js에 정의된 커스텀 색상 (예: theme.extend.colors.primary)으로 대체하여 유지보수성을 높입니다.
    • 일관된 아이콘 라이브러리 사용: package.jsonlucide-react가 있으므로, src/constant/icons.tsx에서 직접 SVG를 정의하는 대신 lucide-react 컴포넌트(예: ShoppingCart, ChevronUp)를 활용하여 일관성을 확보하고 코드를 간결하게 만듭니다.
    • constants 디렉토리 구조 개선: src/constant/cartitems.tssrc/data/cartItems.ts로, src/constant/icons.tsxsrc/components/icons/index.tsx 등으로 이동하여 파일의 역할에 맞는 디렉토리 및 명칭을 사용합니다.
  7. 라우터 구조 개선 (Layout Component 도입):

    • 향후 페이지가 늘어날 것을 대비하여 src/App.tsx에서 Navbar를 포함하는 Layout 컴포넌트를 만들어 라우팅 구조를 더 명확하고 확장 가능하게 만듭니다.
    // src/components/Layout.tsx (새 파일)
    import { Outlet } from 'react-router-dom';
    import Navbar from '../page/navbar';
    const Layout = () => (<div><Navbar /><Outlet /></div>);
    export default Layout;
    
    // src/App.tsx
    import Layout from './components/Layout';
    const App = () => (<Router><Routes><Route path="/" element={<Layout />}><Route index element={<Cart />} /></Route></Routes></Router>);
  8. 애플리케이션 초기화 안정성 강화:

    • src/main.tsx에서 document.getElementById('root')!와 같이 Non-null assertion(!)을 사용하는 대신, root 엘리먼트의 존재 여부를 명시적으로 확인하고 렌더링하도록 하여 애플리케이션의 안정성을 높입니다.
    const rootElement = document.getElementById('root');
    if (rootElement) {
      createRoot(rootElement).render(<StrictMode><App /></StrictMode>);
    } else {
      console.error("ID가 'root'인 엘리먼트를 찾을 수 없습니다. 애플리케이션을 초기화할 수 없습니다.");
    }

전반적으로 코드는 잘 작성되어 있고 학습 과정의 흔적이 흥미롭습니다. 위 개선 제안들을 통해 코드의 안정성, 유지보수성, 성능, 그리고 사용자 접근성을 한층 더 향상시킬 수 있을 것입니다. 수고 많으셨습니다!

Copy link
Collaborator

@woojo230 woojo230 left a comment

Choose a reason for hiding this comment

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

useReducerPage는 page로 분리하기 보다는 훅으로 분리해주는 것이 좋은 것 같습니다

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.

2 participants