저번 실습에선 Redux Toolkit을 사용해서 플레이리스트 장바구니와 모달 상태를 관리했다.
이번엔 기능은 그대로 유지하되 상태 관리 방식을 Redux Toolkit에서 Zustand로 리팩토링하였다.
이번 미션의 핵심은 단순히 라이브러리를 바꾸는 것이 아닌, Redux에서 사용하던 전역 상태 관리 흐름을 Zustand 방식으로 다시 설계해보는 것이다.
이번 실습에서 진행한 작업은 다음과 같다.
- Zudstand 설치
- Redux Toolkit, React Redux 제거
- Redux Provider 제거
- cartSlice, modalSlice 제거
- 장바구니 상태와 모달 상태를 하나의 Zustand Store로 통합
- 기존 기능과 화면 동작은 그대로 유지
1. Zustand Store 생성
Redux Toolkit에선 cartSlice, modalSlice를 각각 따로 만들었지만, Zustand에선 하나의 store안에 필요한 상태와 액션을 함께 정의하였다.
import { create } from 'zustand'
import cartItems, { type CartItem } from '../constants/cartItems'
Store에서 관리하는 상태는 아래 과 같다
interface PlaylistStore {
cartItems: CartItem[]
amount: number
total: number
isOpen: boolean
increase: (id: string) => void
decrease: (id: string) => void
removeItem: (id: string) => void
clearCart: () => void
openModal: () => void
closeModal: () => void
}
여기서 cartItems, amount, total은 장바구니 상태고 isOpen은 모달의 열림/닫힘 상태다.
2. 초기값 설정
기존 Redux에서 사용하던 Mock data를 그대로 Zustand store의 초기값으로 사용했다.
const initialTotals = calculateTotals(cartItems)
export const usePlaylistStore = create<PlaylistStore>((set) => ({
cartItems,
amount: initialTotals.amount,
total: initialTotals.total,
isOpen: false,
...
}))
처음 렌더링 시 전체 수량과 총 금액이 바로 계산되어 있어야 하므로 calculateToals 함수를 만들어 초기값을 계산했다.
3. 합계 계산 함수
장바구니의 총 수량과 총 금액을 계산하는 로직은 별도 함수로 분리한다.
const calculateTotals = (items: CartItem[]) =>
items.reduce(
(totals, item) => {
totals.amount += item.amount
totals.total += item.amount * Number(item.price)
return totals
},
{ amount: 0, total: 0 },
)
Mock data의 price값이 문자열이기 때문에 Number(item.price)로 변환해서 계산했다.
4. 장바구니 액션 옮기기
Redux toolkit에선 reducer안에서 increase, decrease, removeItem, clearCart를 정의했다.
Zustand에선 store안에 액션 함수를 직접 정의한다.
예를 들어 수량 증가는 다음과 같이 작성
increase: (id) =>
set((state) => {
const updatedItems = state.cartItems.map((item) =>
item.id === id ? { ...item, amount: item.amount + 1 } : item,
)
const totals = calculateTotals(updatedItems)
return {
cartItems: updatedItems,
...totals,
}
}),
Redux Toolkit에선 immer덕에 item.amount +=1 처럼 직접 수정하는 것처럼 작성할 수 있었으나, Zustand에선 상태를 새 배열로 만들어 반환하는 방식으로 작성하였다.
수량 감소는 감소 후 수량이 0되면 자동으로 제거되도록 구현
decrease: (id) =>
set((state) => {
const updatedItems = state.cartItems
.map((item) =>
item.id === id ? { ...item, amount: item.amount - 1 } : item,
)
.filter((item) => item.amount > 0)
const totals = calculateTotals(updatedItems)
return {
cartItems: updatedItems,
...totals,
}
}),
삭제, 전체 삭제도 동일하게 Zustand로 옮겼다.
removeItem: (id) =>
set((state) => {
const updatedItems = state.cartItems.filter((item) => item.id !== id)
const totals = calculateTotals(updatedItems)
return {
cartItems: updatedItems,
...totals,
}
}),
clearCart: () =>
set({
cartItems: [],
amount: 0,
total: 0,
}),
모달 상태도 전환
isOpen: false,
openModal: () =>
set({
isOpen: true,
}),
closeModal: () =>
set({
isOpen: false,
}),
이렇게 하면 장바구 상태와 모달 상태를 하나의 Store에서 함께 관리할 수 있다. 모달의 "네" 버튼에선 기존과 동일하게 장바구니 비운 뒤 모달을 닫는다.
const handleConfirm = () => {
clearCart()
closeModal()
}
컴포넌트에서 Zustand Store사용
Redux toolit 사용할 땐 useSeletor, useDispatcher를 사용했다. Zustand로 바꾼 뒤엔 직접 만든 Store hook을 호출해서 필요한 상태와 액션을 가져온다.
const {
cartItems,
amount,
total,
isOpen,
increase,
decrease,
removeItem,
openModal,
} = usePlaylistStore()
이후 버튼 이벤트에서도 distpatch 사용하지 않고 Store에서 가져온 함수를 바로 호출했다
onClick={() => increase(item.id)}
전체 삭제 버튼도 마찬가지
onClick={openModal}
5. Redux 관련 코드 제거
다음 파일들은 제거하였다.
- features/cart/cartSlice.ts
- features/modal/modalSlice.ts
- store/store.ts
- hooks.ts
6. Redux와 Zustand 비교
Redux Toolkit은 구조가 명확하고 상태 변경 흐름이 예측 가능하다.
Slice, Store, Provider, Dispatch구조가 정해져 있어 규모가 커질수록 관리하기 좋다
반면 Zustand는 훨씬 가볍고 코드량이 적다
Provider 설정 없이 Store hook을 바로 사용할 수 있고, 상태와 액션을 하나의 함수 안에 모아둘 수 있어서 작은 프로젝트에선 더 간결하게 느껴졌다.
7. 마무리
이번 실습을 통해 같은 기능이라도 상태 관리 라이브러리에 따라 코드 구조가 어떻게 달라지는지 비교해볼 수 있었다.
Redux Toolkit은 정해진 패턴과 명확한 구조가 장점이고, Zustand는 간결한 사용성과 낮은 설정 비용이 장점이었다.
특히 Zustand에선 Store hook하나만으로 상태와 액션을 가져와 사용할 수 있어서 코드가 훨씬 단순해졌다.
'Web' 카테고리의 다른 글
| 영화 검색 사이트 + 성능 최적화 (useCallback, useMemo, React.memo) (0) | 2026.06.05 |
|---|---|
| Modal Slice 활용하여, 모달 기능 추가 (0) | 2026.05.30 |
| Redux Toolkit으로 Play List 장바구니 생성 (0) | 2026.05.29 |
| TanStack Query 실습 (0) | 2026.05.09 |
| OAuth 로그인 구현 (Google) (0) | 2026.05.01 |