1. 오늘 학습 주제
React에서 전역 상태를 더 간단하게 관리할 수 있는 Zustand를 학습했다.
기존에는 부모에서 자식으로 props를 계속 내려주는 방식으로 상태를 전달했지만, Zustand를 사용하면 필요한 컴포넌트가 store에서 직접 상태를 꺼내 쓸 수 있다.
- Zustand store의 기본 구조
- 전역 상태와 액션을 한 곳에서 관리하는 방법
- props drilling 없이 여러 컴포넌트에서 상태를 공유하는 방법
- store를 역할별로 나누는 방법
- Zustand에서 비동기 액션을 처리하는 방법
- Zustand와 함께 자주 언급되는 Immer의 역할
2. Zustand란
Zustand는 React에서 전역 상태를 간단하게 관리할 수 있도록 도와주는 상태 관리 라이브러리이다.
Redux보다 설정이 훨씬 간단하고, 필요한 상태와 함수만 store에 정의한 뒤 바로 꺼내 쓸 수 있다는 점이 특징이다.
import { create } from "zustand";
정리
- Zustand는 가볍고 단순한 전역 상태 관리 도구이다.
- 복잡한 reducer나 action type 없이 상태와 함수를 함께 정의할 수 있다.
- 필요한 컴포넌트에서 바로 store를 구독해 사용할 수 있다.
3. 기본 Store 생성
기본 예제에서는 create()를 사용해 count, text, 그리고 상태를 바꾸는 액션들을 함께 정의했다.
export const useStore = create((set) => ({
count: 0,
text: "",
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
setText: (value) => set({ text: value })
}));
정리
- Zustand store는 create()로 만든다.
- 상태값과 상태 변경 함수를 한 객체 안에 함께 넣는다.
- set()을 사용해 상태를 업데이트한다.
4. Zustand에서 상태 읽기
컴포넌트에서는 store 훅을 호출해 필요한 상태와 액션을 꺼내 쓸 수 있다.
const { count, increase, decrease, text, setText } = useStore();
이후 일반 state처럼 화면에 출력하거나 버튼 이벤트에 연결할 수 있다.
<h2>{count}</h2>
<button onClick={increase}>증가</button>
<button onClick={decrease}>감소</button>
- store에서 필요한 값만 꺼내 사용할 수 있다.
- 상태값과 액션을 같은 훅에서 함께 사용할 수 있다.
- 로컬 state처럼 보이지만 실제로는 전역 상태이다.
5. Props Drilling 문제와 Zustand
기본 예제에서는 Parent -> Child -> GrandChild 구조였지만, 실제 상태는 GrandChild에서 바로 store를 통해 가져왔다.
export default function Parent() {
return <Child />;
}
export default function Child() {
return <GrandChild />;
}
const { count, increase, decrease, text, setText } = useStore();
- 부모에서 자식으로 props를 계속 넘기지 않아도 된다.
- 필요한 컴포넌트가 직접 전역 상태에 접근할 수 있다.
- 컴포넌트 트리가 깊어져도 상태 전달이 단순해진다.
6. Zustand와 React state의 차이
React의 useState는 컴포넌트 내부 상태를 관리할 때 적합하다.
반면 Zustand는 여러 컴포넌트가 함께 써야 하는 전역 상태를 관리할 때 유리하다.
- useState는 컴포넌트 단위 상태 관리에 적합하다.
- Zustand는 여러 컴포넌트가 공유하는 상태에 적합하다.
- 공통 상태가 많아질수록 Zustand의 장점이 커진다.
7. Store 분리하기
두 번째 예제에서는 store를 하나로 크게 만들지 않고 역할별로 나누어 관리했다.
- useUIStore
- useUserStore
- useCartStore
이렇게 나누면 상태의 책임이 명확해진다.
- UI 관련 상태는 UI store
- 사용자 정보는 user store
- 장바구니 데이터는 cart store
- 기능별로 store를 분리하면 유지보수가 쉬워진다.
8. UI Store
UI store에서는 모달 열기/닫기, 사이드바 토글처럼 화면 상태를 관리했다.
export const useUIStore = create((set) => ({
isModalOpen: false,
isSidebarOpen: false,
openModal: () => set({ isModalOpen: true }),
closeModal: () => set({ isModalOpen: false }),
toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
}));
- UI 상태는 서버 데이터와 별개로 관리할 수 있다.
- 모달, 사이드바, 탭 선택 같은 상태를 전역으로 다루기 좋다.
- UI 로직이 여러 컴포넌트에 흩어지는 것을 막을 수 있다.
9. User Store
User store에서는 로그인, 로그아웃, 사용자 정보 저장 로직을 관리했다.
export const useUserStore = create((set) => ({
user: null,
loading: false,
login: (userData) => set({ user: userData }),
logout: () => set({ user: null }),
setUser: (user) => set({ user }),
}));
- 로그인한 사용자 정보처럼 앱 전역에서 필요한 데이터 관리에 적합하다.
- 인증 상태를 여러 페이지와 컴포넌트에서 공유할 수 있다.
- 액션도 함께 모아두면 상태 변경 흐름이 명확해진다.
10. Cart Store
Cart store에서는 장바구니 항목 추가, 삭제, 비우기 로직을 관리했다.
export const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({
items: [...state.items, item],
})),
removeItem: (id) => set((state) => ({
items: state.items.filter((item) => item.id !== id),
})),
clearCart: () => set({ items: [] }),
}));
- 배열 상태를 다룰 때도 Zustand 안에서 직접 관리할 수 있다.
- 추가는 전개 연산자, 삭제는 filter() 패턴을 사용했다.
- 상태 업데이트 시 기존 배열을 직접 수정하지 않고 새 배열을 만들어야 한다.
11. Zustand에서 불변성
Zustand를 쓰더라도 React 상태 관리의 기본 원칙인 불변성은 여전히 중요하다.
예제에서도 배열에 push()를 직접 하지 않고, 새 배열을 만들어 교체했다.
items: [...state.items, item]
- 기존 상태를 직접 수정하면 안 된다.
- 새로운 객체나 배열을 만들어 상태를 바꾸는 방식이 필요하다.
- Zustand도 React 생태계 안에서 불변성 원칙을 따르는 것이 중요하다.
12. 비동기 액션 처리
Zustand는 store 안에서 비동기 함수도 바로 정의할 수 있다.
예제에서는 fetchUser라는 비동기 액션을 store 안에 넣어 사용자 정보를 불러왔다.
fetchUser: async () => {
set({ loading: true });
try {
const res = await fetch('<https://jsonplaceholder.typicode.com/users/1>');
const data = await res.json();
set({ user: data });
} catch (error) {
console.error('유저 정보 조회 실패', error);
} finally {
set({ loading: false });
}
}
- Zustand store 안에 async 함수를 넣을 수 있다.
- 비동기 요청 전후로 loading 상태를 관리할 수 있다.
- 서버 데이터를 store에 직접 저장하는 구조를 만들 수 있다.
13. useEffect와 Zustand 비동기 액션
비동기 페이지 예제에서는 컴포넌트가 마운트되면 fetchUser()를 실행하도록 구성했다.
useEffect(() => {
fetchUser();
}, [fetchUser]);
그리고 로딩 상태에 따라 화면을 분기했다.
if (loading) {
return <h1>데이터 로딩중...</h1>;
}
- API 호출 시점은 useEffect와 함께 제어할 수 있다.
- 상태 저장은 store가 담당하고, 컴포넌트는 화면 렌더링에 집중한다.
- 전역 상태와 비동기 로직을 함께 다루는 흐름을 이해할 수 있다.
14. Zustand의 장점
예제를 통해 Zustand의 장점을 체감할 수 있었다.
- 설정이 간단하다.
- boilerplate 코드가 적다.
- props drilling을 줄일 수 있다.
- store를 기능별로 쉽게 나눌 수 있다.
- 동기/비동기 액션을 함께 관리할 수 있다.
- 작은 프로젝트부터 중간 규모 프로젝트까지 가볍게 적용하기 좋다.
15. Immer란
Immer는 불변성을 유지하면서도 마치 객체를 직접 수정하는 것처럼 코드를 작성할 수 있게 도와주는 라이브러리이다.
원래는 아래처럼 새 배열이나 새 객체를 만들어야 한다.
set((state) => ({
items: [...state.items, item]
}))
Immer를 사용하면 내부적으로는 불변성을 지키면서도 더 직관적으로 쓸 수 있다.
개념적으로는 아래처럼 작성하는 느낌이다.
draft.items.push(item);
- Immer는 불변성 관리를 쉽게 만들어 준다.
- 코드가 더 직관적이고 읽기 쉬워진다.
- 상태 구조가 깊고 복잡할수록 장점이 커진다.
- Zustand와 함께 쓰면 복잡한 객체 업데이트를 더 편하게 작성할 수 있다.
16. Zustand와 Immer를 함께 쓰는 이유
Zustand만으로도 상태 관리는 가능하지만, 배열이나 중첩 객체가 복잡해질수록 불변성 코드를 직접 쓰는 것이 번거로워질 수 있다.
이럴 때 Immer를 같이 사용하면 상태를 더 자연스럽게 수정할 수 있다.
- Zustand는 상태 저장 구조를 단순하게 만든다.
- Immer는 상태 업데이트 코드를 단순하게 만든다.
- 둘을 함께 쓰면 특히 복잡한 전역 상태를 다룰 때 편해진다.
17. 오늘 배운 핵심 정리
- Zustand는 React에서 전역 상태를 간단하게 관리할 수 있는 라이브러리이다.
- create()로 store를 만들고, 상태와 액션을 함께 정의할 수 있다.
- 필요한 컴포넌트가 store에서 직접 상태를 읽기 때문에 props drilling을 줄일 수 있다.
- count, text 같은 기본 상태부터 모달, 유저, 장바구니 같은 실제 앱 상태까지 관리할 수 있다.
- store는 기능별로 분리하면 구조가 더 명확해진다.
- Zustand에서도 배열과 객체는 불변성을 지키며 업데이트해야 한다.
- store 안에 비동기 액션을 직접 정의할 수 있다.
- loading, user 같은 상태를 함께 관리하면 비동기 화면 분기가 쉬워진다.
- Immer는 불변성을 유지하면서도 마치 직접 수정하는 것처럼 상태 업데이트를 작성하게 도와준다.
- Zustand와 Immer를 함께 사용하면 복잡한 상태 업데이트를 더 읽기 쉽게 만들 수 있다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Mini Cart 프로젝트, json-server, API Layer 분리, Zustand persist (0) | 2026.04.28 |
|---|---|
| [TIL]React Query, json-server, Zustand persist, CRUD 테스트 (0) | 2026.04.24 |
| [TIL]React Custom Hook과 Next.js App Router (0) | 2026.04.22 |
| [TIL]React API 통신과 데이터 컴포넌트 (0) | 2026.04.21 |
| [TIL]React Hooks 심화와 비동기 프로그래밍 (0) | 2026.04.20 |