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

25강 / 전체 28강

성능 기초 — memo

7분 읽기 조회 0

Zustand로 전역 상태를 단순하고 강력하게 관리하는 방법, 스토어 설계 패턴, devtools 연동, 슬라이스 패턴까지 실전 Context 대안을 배웁니다.

🤷 Context의 한계와 Zustand의 등장

🤷 Context의 한계와 Zustand의 등장

Context API는 테마, 언어처럼 드물게 바뀌는 전역 값에 적합합니다. 하지만 자주 바뀌는 상태를 Context에 넣으면 해당 Context를 소비하는 모든 컴포넌트가 리렌더링되는 성능 문제가 생깁니다. 또한 코드 구조가 Provider 중첩으로 복잡해집니다.

Zustand(주스탄드)는 이 문제를 단순하게 해결하는 상태 관리 라이브러리입니다. 독일어로 "상태"를 의미합니다. Provider 없이, 보일러플레이트 없이, 필요한 값만 구독해 그것이 바뀔 때만 리렌더링합니다.

npm install zustand

🏗️ 스토어 만들기

// store/useCartStore.ts
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

interface CartItem {
  id: number
  name: string
  price: number
  quantity: number
}

interface CartStore {
  items: CartItem[]
  isOpen: boolean
  // 액션
  addItem: (item: Omit<CartItem, 'quantity'>) => void
  removeItem: (id: number) => void
  updateQuantity: (id: number, quantity: number) => void
  clearCart: () => void
  toggleCart: () => void
  // 파생 값 (getters)
  totalPrice: () => number
  totalCount: () => number
}

export const useCartStore = create<CartStore>()(
  devtools(
    (set, get) => ({
      items: [],
      isOpen: false,

      addItem: (newItem) => set((state) => {
        const existing = state.items.find((i) => i.id === newItem.id)
        if (existing) {
          return {
            items: state.items.map((i) =>
              i.id === newItem.id ? { ...i, quantity: i.quantity + 1 } : i
            ),
          }
        }
        return { items: [...state.items, { ...newItem, quantity: 1 }] }
      }),

      removeItem: (id) => set((state) => ({
        items: state.items.filter((i) => i.id !== id),
      })),

      updateQuantity: (id, quantity) => set((state) => ({
        items: quantity <= 0
          ? state.items.filter((i) => i.id !== id)
          : state.items.map((i) => (i.id === id ? { ...i, quantity } : i)),
      })),

      clearCart: () => set({ items: [] }),
      toggleCart: () => set((state) => ({ isOpen: !state.isOpen })),

      totalPrice: () => get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
      totalCount: () => get().items.reduce((sum, i) => sum + i.quantity, 0),
    }),
    { name: 'CartStore' }  // devtools 표시명
  )
)

🔌 컴포넌트에서 사용하기

Provider 설정 없이 바로 import해서 사용합니다:

// 필요한 값만 선택해서 구독 (성능 최적화의 핵심)
function CartButton() {
  // totalCount만 구독 — items가 바뀌어도 totalCount가 같으면 리렌더 없음
  const totalCount = useCartStore((state) => state.totalCount())
  const toggleCart = useCartStore((state) => state.toggleCart)

  return (
    <button onClick={toggleCart} className="relative p-2">
      🛒
      {totalCount > 0 && (
        <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
          {totalCount}
        </span>
      )}
    </button>
  )
}

function ProductCard({ product }: { product: { id: number; name: string; price: number } }) {
  const addItem = useCartStore((state) => state.addItem)

  return (
    <div className="border rounded-xl p-4">
      <h3 className="font-bold">{product.name}</h3>
      <p className="text-gray-500">{product.price.toLocaleString()}원</p>
      <button
        onClick={() => addItem(product)}
        className="mt-3 w-full py-2 bg-indigo-600 text-white rounded-lg text-sm"
      >
        장바구니 추가
      </button>
    </div>
  )
}

🍰 슬라이스 패턴 — 스토어 분리

스토어가 커지면 파일을 나눠서 슬라이스(slice)로 관리합니다:

// store/slices/authSlice.ts
import { StateCreator } from 'zustand'

export interface AuthSlice {
  user: { id: string; name: string } | null
  login: (user: AuthSlice['user']) => void
  logout: () => void
}

export const createAuthSlice: StateCreator<AuthSlice> = (set) => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null }),
})

// store/useStore.ts — 여러 슬라이스 합치기
import { create } from 'zustand'
import { createAuthSlice, AuthSlice } from './slices/authSlice'
import { createCartSlice, CartSlice } from './slices/cartSlice'

export const useStore = create<AuthSlice & CartSlice>()((...a) => ({
  ...createAuthSlice(...a),
  ...createCartSlice(...a),
}))

📊 Context vs Zustand 비교

기준Context APIZustand
설정 코드Provider, createContextcreate() 한 줄
리렌더링소비자 전체 리렌더구독한 값이 바뀔 때만
외부 접근훅 안에서만훅 + 모듈 직접 접근 가능
DevTools없음Redux DevTools 지원
러닝커브낮음낮음 (더 단순함)

✅ 정리

  • Zustand: Provider 없이 create()로 스토어 정의, 바로 사용.
  • 필요한 값만 선택적으로 구독 → 불필요한 리렌더 방지.
  • devtools 미들웨어로 Redux DevTools에서 상태 변화를 추적합니다.
  • 자주 바뀌는 전역 상태에서는 Context 대신 Zustand를 사용하세요.

관련 주제

  • React.memo
  • useMemo
  • useCallback
  • 불필요한 리렌더링 방지
  • 과용 주의
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나
이전 24강비동기 상태 관리 패턴다음 26강 에러 처리와 경계

댓글

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