Zustand vs Redux: 실전 비교
Zustand는 가볍고 직관적인 상태 관리 라이브러리입니다. 이 글에서는 Zustand의 기본 사용법과 Redux와의 차이점을 실전 코드와 함께 살펴봅니다.
Zustand 기본 사용법
섹션 제목: “Zustand 기본 사용법”Zustand는 상태를 유지하는 store를 만드는 데 사용됩니다. 불변 상태 모델을 기반으로 합니다.
Store 생성
섹션 제목: “Store 생성”import { create } from "zustand";
export const useStore = create(() => ({ count: 0, text: "hello",}));기본 사용 (주의 필요)
섹션 제목: “기본 사용 (주의 필요)”const Component = () => { const { count, text } = useStore(); return <div>count: {count}</div>;};이렇게 사용하면 text 값이 변경되어도 리렌더링이 발생할 수 있습니다.
선택자 함수를 통한 최적화
섹션 제목: “선택자 함수를 통한 최적화”const Component = () => { const count = useStore((state) => state.count); return <div>count: {count}</div>;};선택자 함수를 사용하면 count가 변경될 때만 리렌더링됩니다.
참조 값 선택 시 주의
섹션 제목: “참조 값 선택 시 주의”선택자 함수는 결과값을 비교해서 렌더링 여부를 판단하므로, 참조 값(객체, 배열)을 반환하도록 지정할 경우 주의해야 합니다.
// 주의: 매번 새 객체를 생성하므로 항상 리렌더링 발생const Component = () => { const [{ count }] = useStore((state) => [{ count: state.count }]); return <div>count: {count}</div>;};Store에 액션 포함하기
섹션 제목: “Store에 액션 포함하기”외부에서 setState 사용
섹션 제목: “외부에서 setState 사용”const selectCount1 = (state) => state.count1;
const Counter1 = () => { const count1 = useStore(selectCount1); const inc1 = () => { useStore.setState((prev) => ({ count1: prev.count1 + 1 })); };
return ( <div> count1: {count1} <button onClick={inc1}>+1</button> </div> );};Store 내부에 액션 정의 (권장)
섹션 제목: “Store 내부에 액션 정의 (권장)”type StoreState = { count1: number; count2: number; inc1: () => void; inc2: () => void;};
const useStore = create<StoreState>((set) => ({ count1: 0, count2: 0, inc1: () => set((prev) => ({ count1: prev.count1 + 1 })), inc2: () => set((prev) => ({ count2: prev.count2 + 1 })),}));const selectCount2 = (state) => state.count2;const selectInc2 = (state) => state.inc2;
const Counter2 = () => { const count2 = useStore(selectCount2); const inc2 = useStore(selectInc2);
return ( <div> count2: {count2} <button onClick={inc2}>+1</button> </div> );};파생된 값에 대한 선택자 함수
섹션 제목: “파생된 값에 대한 선택자 함수”const selectCount1 = (state) => state.count1;const selectCount2 = (state) => state.count2;
const TotalCounter = () => { const count1 = useStore(selectCount1); const count2 = useStore(selectCount2);
return <div>total: {count1 + count2}</div>;};위 경우 count1이 +1 증가하고 count2가 -1 감소하면 결과값은 같지만 리렌더링이 발생합니다.
이를 해결하기 위해 파생된 값에 대한 선택자 함수를 사용합니다:
const selectTotal = (state) => state.count1 + state.count2;
const TotalCounter = () => { const total = useStore(selectTotal); return <div>total: {total}</div>;};선택자 함수는 결과를 비교하므로, 합산 결과가 같다면 리렌더링이 방지됩니다.
Zustand vs Redux
섹션 제목: “Zustand vs Redux”둘 다 단방향 데이터 흐름을 기반으로 합니다.
상태 갱신 방법의 차이
섹션 제목: “상태 갱신 방법의 차이”| Zustand | Redux | |
|---|---|---|
| 상태 갱신 | set 함수로 직접 갱신 | 리듀서 기반 (순수 함수) |
| Store 사용 | Hook으로 직접 사용 | useSelector, useDispatch로 간접 사용 |
주요 차이점
섹션 제목: “주요 차이점”| 항목 | Zustand | Redux |
|---|---|---|
| 디렉토리 구조 | 추천 구조 없음 | features 디렉토리 구조 제안, createSlice |
| Immer | 개발자 선택 | 기본 내장 |
| 단방향 흐름 | 개발자 선택 | 단방향 데이터 흐름 기반 |
| 보일러플레이트 | 최소 | 상대적으로 많음 |
| 미들웨어 | 선택적 | devtools, thunk 등 기본 내장 |
Zustand 선택이 적합한 경우
섹션 제목: “Zustand 선택이 적합한 경우”- 가벼운 전역 상태가 필요할 때
- 보일러플레이트를 최소화하고 싶을 때
- 유연한 상태 갱신이 필요할 때
Redux 선택이 적합한 경우
섹션 제목: “Redux 선택이 적합한 경우”- 대규모 앱에서 예측 가능한 상태 흐름이 필요할 때
- 시간 여행 디버깅(DevTools)이 필요할 때
- 팀 내 일관된 구조가 중요할 때
- Zustand는 간결하고 유연한 상태 관리 라이브러리
- 선택자 함수를 통해 필요한 상태만 구독하여 리렌더링 최적화
- 파생된 값에 대한 선택자를 사용하면 불필요한 리렌더링 방지
- Redux와 비교 시 보일러플레이트가 적고 학습 곡선이 낮음
- 프로젝트 규모와 팀 상황에 맞게 선택하는 것이 중요