누구나 로고
オンライン講座企業・団体研修ライブラリトレンドインサイト無料コースお知らせお問い合わせ採用サイト
모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.
로그인무료 회원가입
강좌리액트 입문 — 컴포넌트로 만드는 웹17강

17강 / 전체 28강

Context API

7분 읽기 조회 0

useReducer로 복잡한 상태 로직을 액션 기반으로 관리하는 방법, reducer 패턴의 장점, useState vs useReducer 선택 기준을 실전 예제로 배웁니다.

🤔 언제 useReducer가 필요한가

useState는 단순한 상태 관리에 완벽합니다. 그런데 상태가 복잡해지면 useState만으로 코드가 지저분해집니다. 특히 이런 경우에 useReducer를 고려합니다:

  • 여러 state가 서로 연관되어 함께 업데이트됩니다
  • 다음 상태가 현재 상태의 여러 필드에 의존합니다
  • 상태 업데이트 로직이 여러 핸들러에 분산됩니다
  • 가능한 상태 변경 경우의 수가 명확히 정의됩니다

⚙️ useReducer의 작동 원리

useReducer는 Redux의 아이디어를 리액트에 내장한 것입니다. 핵심 개념:

  • State: 현재 상태 객체
  • Action: 무슨 일이 일어났는지를 설명하는 객체 (type + 선택적 payload)
  • Reducer: (state, action) => newState 형태의 순수 함수
  • Dispatch: 액션을 발생시키는 함수
// 장바구니 상태 관리 예제
interface CartItem {
  id: number
  name: string
  price: number
  quantity: number
}

interface CartState {
  items: CartItem[]
  isOpen: boolean
}

// 액션 타입 정의 (TypeScript discriminated union)
type CartAction =
  | { type: 'ADD_ITEM'; payload: Omit<CartItem, 'quantity'> }
  | { type: 'REMOVE_ITEM'; payload: { id: number } }
  | { type: 'UPDATE_QUANTITY'; payload: { id: number; quantity: number } }
  | { type: 'CLEAR_CART' }
  | { type: 'TOGGLE_CART' }

// 리듀서: 순수 함수 — 항상 새 객체 반환
function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case 'ADD_ITEM': {
      const existing = state.items.find((item) => item.id === action.payload.id)
      if (existing) {
        return {
          ...state,
          items: state.items.map((item) =>
            item.id === action.payload.id
              ? { ...item, quantity: item.quantity + 1 }
              : item
          ),
        }
      }
      return { ...state, items: [...state.items, { ...action.payload, quantity: 1 }] }
    }
    case 'REMOVE_ITEM':
      return { ...state, items: state.items.filter((item) => item.id !== action.payload.id) }
    case 'UPDATE_QUANTITY':
      return {
        ...state,
        items: state.items.map((item) =>
          item.id === action.payload.id
            ? { ...item, quantity: Math.max(0, action.payload.quantity) }
            : item
        ).filter((item) => item.quantity > 0),
      }
    case 'CLEAR_CART':
      return { ...state, items: [] }
    case 'TOGGLE_CART':
      return { ...state, isOpen: !state.isOpen }
    default:
      return state
  }
}

🔌 컴포넌트에서 사용하기

const initialState: CartState = { items: [], isOpen: false }

function ShoppingCart() {
  const [state, dispatch] = useReducer(cartReducer, initialState)

  const totalPrice = state.items.reduce(
    (sum, item) => sum + item.price * item.quantity, 0
  )

  return (
    <div>
      <button onClick={() => dispatch({ type: 'TOGGLE_CART' })}>
        장바구니 {state.items.length > 0 && `(${state.items.length})`}
      </button>

      {state.isOpen && (
        <div className="border rounded p-4">
          {state.items.length === 0 ? (
            <p>장바구니가 비었습니다</p>
          ) : (
            <>
              {state.items.map((item) => (
                <div key={item.id} className="flex items-center justify-between py-2">
                  <span>{item.name}</span>
                  <div className="flex items-center gap-2">
                    <button onClick={() => dispatch({ type: 'UPDATE_QUANTITY', payload: { id: item.id, quantity: item.quantity - 1 } })}>-</button>
                    <span>{item.quantity}</span>
                    <button onClick={() => dispatch({ type: 'UPDATE_QUANTITY', payload: { id: item.id, quantity: item.quantity + 1 } })}>+</button>
                    <button onClick={() => dispatch({ type: 'REMOVE_ITEM', payload: { id: item.id } })}>삭제</button>
                  </div>
                </div>
              ))}
              <div className="border-t pt-2 font-bold">합계: {totalPrice.toLocaleString()}원</div>
              <button onClick={() => dispatch({ type: 'CLEAR_CART' })}>전체 비우기</button>
            </>
          )}
        </div>
      )}
    </div>
  )
}

📊 useState vs useReducer — 선택 기준

기준useStateuseReducer
상태 복잡도단순한 값(숫자, 문자열, 불리언)연관된 여러 필드가 있는 객체
업데이트 패턴독립적인 단일 값 변경여러 필드가 함께 바뀌는 경우
가능한 액션명확하지 않음명확히 정의된 액션 목록이 있음
테스트컴포넌트 단위리듀서 함수 단독 테스트 가능

✅ 정리

  • useReducer: (state, action) => newState 패턴으로 복잡한 상태 관리.
  • dispatch({ type: 'ACTION_TYPE', payload: ... })로 상태 변경을 요청합니다.
  • 리듀서는 순수 함수여야 합니다 — 항상 새 객체를 반환하세요.
  • TypeScript discriminated union으로 액션 타입을 정의하면 타입 안전성이 높아집니다.

관련 주제

  • Props Drilling 문제
  • createContext
  • Provider 패턴
  • useContext
  • 전역 테마 사용자값
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나
이전 16강커스텀 훅 만들기다음 18강 useReducer

댓글

0/1000

불러오는 중...

목차

  1. 1.리액트란 무엇인가
  2. 2.개발 환경 만들기 — Vite
  3. 3.JSX 기초
  4. 4.컴포넌트 만들기
  5. 5.Props로 데이터 전달
  6. 6.State와 useState
  7. 7.이벤트 처리
  8. 8.조건부 렌더링
  9. 9.리스트 렌더링과 key
  10. 10.폼 다루기
  11. 11.useEffect 기초
  12. 12.데이터 가져오기(fetch)
  13. 13.상태 끌어올리기
  14. 14.컴포넌트 합성과 재사용
  15. 15.useRef와 DOM 접근
  16. 16.커스텀 훅 만들기
  17. 17.Context API
  18. 18.useReducer
  19. 19.리스트 앱 실전 ①
  20. 20.리스트 앱 실전 ②
  21. 21.라우팅 — React Router
  22. 22.스타일링 방법들
  23. 23.리스트·상세 페이지 연결
  24. 24.비동기 상태 관리 패턴
  25. 25.성능 기초 — memo
  26. 26.에러 처리와 경계
  27. 27.환경변수와 빌드
  28. 28.배포하기

カスタマーサポート

  • お知らせ
  • よくある質問
  • お問い合わせ
  • コミュニティ

ご利用案内

  • 利用規約
  • プライバシーポリシー
  • 返金ポリシー

サービス

  • 会社紹介
  • 新規登録
  • 新着コース
  • 無料コース
누구나 로고
利用規約プライバシーポリシー返金ポリシー

상호명: NUGUNA  |  대표자: 정우진  |  사업자등록번호: 392-32-01817  |  통신판매업신고: 제 2026-서울양천-0564 호

주소: 서울특별시 양천구 목동서로 100  |  이메일: nugunapass@gmail.com  |  전화: 010-6395-3043

© 2026 NUGUNA. All rights reserved.

KB예금주인증관리자
ホームコースライブラリお問い合わせMY