Zustand의 store, action, selector, persist를 어떤 순서로 배우나요?
Zustand는 작은 state와 변경 함수인 action을 가진 store부터 만들고, selector로 구독 범위를 좁힌 뒤 필요한 값만 persist로 저장하는 순서로 익힙니다. 먼저 어떤 값이 공유 상태인지와 누가 바꾸는지를 정합니다.
적용 조건·범위: 판단 기준은 "전역 상태인가?"보다 "여러 위치에서 공유해야 하고, 생명주기를 컴포넌트 하나에 묶기 어려운가?"에 가깝습니다.
본문 근거: Vite Getting Started · Zustand 공식 저장소 · zustand.docs.pmnd.rs
이 기술만 따로 실행하기
이 개념 직접 실행
상태를 바꾸고 화면에 반영되는 결과를 확인합니다.
예제 크게 보기 (새 탭)직접 실행: 환경 준비와 전체 파일
선수 기술: React 함수 컴포넌트·이벤트와 TypeScript 객체 타입입니다. 이전 TaskFlow 프로젝트는 단독 실습에 필요하지 않습니다. Node.js는 22.12 이상 또는 설치할 Vite 템플릿의 요구 버전을 충족해야 합니다. 터미널에서 준비 상태부터 확인합니다.
node --version
npm --version
npm create vite@latest zustand-lab -- --template react-ts
cd zustand-lab
npm install
npm install zustand@5
아직 같은 이름의 폴더가 없는 위치에서 새 연습 앱을 만듭니다. 생성된 src/main.tsx는 그대로 두고 src/App.tsx를 다음 내용으로 교체합니다.
import { create } from 'zustand';
type Selection = { selectedId: string | null; select: (id: string | null) => void };
const useSelection = create<Selection>()((set) => ({
selectedId: null,
select: (selectedId) => set({ selectedId }),
}));
function SelectedLabel() {
const id = useSelection((state) => state.selectedId);
return <p>선택: {id ?? '없음'}</p>;
}
export default function App() {
const select = useSelection((state) => state.select);
return (
<main>
<SelectedLabel />
<button onClick={() => select('task-1')}>첫 작업 선택</button>
<button onClick={() => select(null)}>선택 해제</button>
</main>
);
}
npm run dev
npm run build
터미널이 알려 준 로컬 주소를 엽니다. 첫 작업 선택 → 선택 해제를 누르면 선택: 없음 → task-1 → 없음으로 바뀌어야 합니다. 이 작은 앱에서 store 생성, state, action, selector 순서를 먼저 익힌 뒤 저장이 필요할 때 persist로 넘어갑니다.
자가 확인: 선택 표시 컴포넌트가 직접 값을 바꾸지 않고 action을 호출하는 이유를 설명하세요. 다른 컴포넌트에서도 같은 변경 규칙을 재사용하기 위해서입니다.
기존 검증 기록: 2026-09-28 새 Vite React·TypeScript 프로젝트를 생성하고 이 App.tsx를 넣어 npm run build를 통과했습니다. jsdom 버튼 상태 변화도 확인했으며 실제 브라우저 화면 검증은 별도입니다.
환경 명령 기준: Vite Getting Started, Zustand 공식 저장소 (2026-09-28 확인).
Zustand는 무엇을 해결하는가
React의 useState는 컴포넌트 내부 상태를 관리하기 좋습니다.
하지만 같은 상태를 여러 컴포넌트가 사용하기 시작하면 상태를 어디에 둘지 고민해야 합니다.
예를 들어 다음 값이 있다고 가정합니다.
selectedTaskId
isTaskModalOpen
taskFilter
theme
sidebarOpen
이 값들을 서로 멀리 떨어진 컴포넌트가 함께 사용한다면 props를 여러 단계 전달하거나 Context를 만들어야 합니다.
Zustand는 이런 클라이언트 전역 상태를 store에 두고 필요한 컴포넌트가 직접 구독하도록 만들 수 있습니다.
기본 흐름은 다음과 같습니다.
store 생성
↓
state 정의
↓
action 정의
↓
컴포넌트에서 필요한 값만 selector로 구독
↓
필요한 상태만 persist
↓
devtools / middleware / migration 적용
Zustand 학습 순서
BlogFlow의 Zustand 과정은 다음 순서로 진행합니다.
초급
- 전체 학습 로드맵
- React state vs Zustand
- Zustand 개념과 Store 설계
- Zustand 설치와 첫 Store
- state 읽기와 변경
중급
- action으로 변경 로직 분리
- selector로 필요한 상태만 구독
- 리렌더링 원리와 selector 최적화
- 상태 변경 후 화면이 안 바뀌는 문제 해결
- persist로 새로고침 후 상태 복원
고급
- combine과 immer
- subscribeWithSelector와 devtools
- persist migration
- 실무 Store 설계 점검
- TaskFlow Zustand 최종 통합
React state와 Zustand의 경계
모든 상태를 Zustand에 넣는 것이 좋은 구조는 아닙니다.
다음 값은 보통 컴포넌트 로컬 state가 더 자연스럽습니다.
input에 입력 중인 문자열
한 컴포넌트에서만 사용하는 열림/닫힘
잠깐 표시되는 hover 상태
로컬 폼의 임시 입력값
반대로 다음처럼 여러 화면이나 컴포넌트가 공유해야 하는 값은 Zustand 후보가 됩니다.
selectedTaskId
전역 modal 상태
사용자 UI 설정
여러 컴포넌트가 공유하는 필터
클라이언트에서 오래 유지해야 하는 선택 상태
판단 기준은 “전역 상태인가?”보다 “여러 위치에서 공유해야 하고, 생명주기를 컴포넌트 하나에 묶기 어려운가?”에 가깝습니다.
서버 상태와 Zustand를 구분해야 하는 이유
API에서 가져온 원본 데이터는 클라이언트 상태와 성격이 다릅니다.
예를 들어 서버에서 tasks를 가져온다면 다음 문제를 함께 다뤄야 합니다.
- 로딩
- 에러
- 캐시
- refetch
- stale 상태
- mutation 후 동기화
이런 서버 상태는 TanStack Query가 더 적합합니다.
따라서 이 과정에서 원칙은 다음과 같습니다.
서버에서 가져온 원본 데이터
→ TanStack Query
selectedTaskId
modal
UI filter
사용자 인터페이스 설정
임시 편집 상태
→ Zustand
현재 TaskFlow의 tasks는 로컬 학습 데이터이므로 Zustand 이전을 연습하지만, 이후 서버 데이터로 바뀐다면 TanStack Query가 원본을 담당하도록 역할을 다시 나눕니다.
TaskFlow 15회 확장 로드맵
시작 상태는 다음과 같습니다.
TaskContext
TaskProvider
taskReducer
dispatch
useContext
과정이 끝나면 다음 구조를 목표로 합니다.
useTaskStore
state
actions
selectors
persist
devtools
migration
명확한 store 경계
전체 흐름은 다음과 같습니다.
React reducer + Context
→ Zustand 도입 기준 정리
→ Store 설계
→ Zustand 설치
→ state 이전
→ action 이전
→ selector 적용
→ 리렌더링 측정
→ 잘못된 업데이트 수정
→ persist
→ combine / immer 판단
→ subscribeWithSelector / devtools
→ persist migration
→ store 경계 재검토
→ 최종 TaskFlow 통합
자주 하는 시작 단계 실수
Context를 전부 Zustand로 바꾸려고 합니다
Context 자체가 잘못된 것은 아닙니다.
테마, 인증 Provider, 라이브러리 Provider처럼 Context가 자연스러운 경우도 많습니다.
Zustand는 Context를 없애는 도구가 아니라, 여러 컴포넌트가 공유하는 클라이언트 상태를 더 단순하게 다루는 선택지입니다.
서버 데이터를 store에 복제합니다
TanStack Query로 받은 데이터를 다시 Zustand에 복사하면 두 상태 중 무엇이 최신인지 판단하기 어려워집니다.
원본 데이터의 소유자를 하나로 정하는 것이 중요합니다.
처음부터 middleware를 모두 사용합니다
persist, devtools, immer, subscribeWithSelector는 필요한 시점에 도입합니다.
기본 store와 selector를 이해하기 전에 middleware를 많이 붙이면 문제가 생겼을 때 원인을 찾기 어렵습니다.
학습 완료 체크리스트
- Zustand를 클라이언트 전역 상태 관리 도구라고 설명할 수 있습니다.
- 로컬 state와 전역 store를 구분할 수 있습니다.
- 서버 상태와 클라이언트 상태의 차이를 설명할 수 있습니다.
- store → action → selector → persist 순서를 설명할 수 있습니다.
- TaskFlow가 reducer + Context에서 Zustand로 어떻게 바뀌는지 이해했습니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 여러 화면이 공유하는 상태를 관리합니다.
코드를 크게 바꾸지 않고, 앞으로 Zustand로 옮길 상태와 남겨둘 상태를 분류합니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
TaskFlow 직접 실행
docs/state-roadmap.md에 Context 작업 데이터와 로컬 입력의 현재 소유자를 기록합니다. 실행 코드의 동작은 이전과 같습니다.
예제 크게 보기 (새 탭)중간부터 시작하는 전체 실습 파일
기존 React Context TaskFlow의 작업 목록·추가·완료·삭제 화면을 이어서 확장합니다.
이 글의 시작본은 직전 프로젝트 단계 완료본 전체입니다. 이미 직전 단계를 마쳤다면 그 폴더를 이어 쓰셔도 됩니다. Node.js 24.x와 npm을 사용하며 패키지 버전은 package-lock.json에 고정했습니다.
시작본에서 직접 바꾸기
개념·경계 설계 단계라 실행 앱을 바꾸지 않고 ZIP의 문서를 직접 작성·비교합니다.
docs/state-roadmap.md— 이전 단계에 없던 파일을 만듭니다.
변경 이유와 결과: docs/state-roadmap.md에 Context 작업 데이터와 로컬 입력의 현재 소유자를 기록합니다. 실행 코드의 동작은 이전과 같습니다.
완료본의 docs/changes.patch에서 시작·완료 차이를, docs/current-step.md에서 파일별 적용 위치를 확인할 수 있습니다. 아래 전체 파일을 같은 경로에 반영한 다음 실행하세요.
docs/state-roadmap.md 전체 파일
# 이전 계획
tasks: 현재 Context → 06에서 Zustand → 서버 도입 시 Query.
입력: TaskForm 로컬 state. 선택·모달·필터: 공유 UI 후보.
완료 확인
- 시작본은 직전 완료본과 동일한 ZIP입니다. 압축을 푼 뒤
npm ci로 잠금 파일의 의존성을 설치합니다. npm run dev실행 후 터미널의 localhost 주소를 엽니다.- docs/state-roadmap.md에 Context 작업 데이터와 로컬 입력의 현재 소유자를 기록합니다. 실행 코드의 동작은 이전과 같습니다.
npm run typecheck와npm run build가 성공하면 다음 프로젝트 단계로 갑니다.
원문에 있는 기존 체크포인트 검증 기록은 TypeScript 검사·프로덕션 빌드와 실제 소스를 사용한 JSDOM/상태 시나리오 검증 통과입니다. 별도 실제 브라우저 화면 검수는 이 파일 검증 범위에 포함하지 않았습니다.
아래 부분 예제는 핵심 변경을 설명합니다. 실행에 필요한 전체 파일과 보조 타입·API는 위 완료본에서 확인하세요.
이어서 만들기: React useReducer와 Context 실습 작업 목록 상태 여러 컴포넌트 공유의 적용 결과에서 시작합니다. 아래 체크포인트 이름은 단계 식별자이며 다운로드 파일명이나 Git 명령이 아닙니다.
React useReducer·Context 글의 TaskProvider·taskReducer·dispatch 구성을 기준으로 상태 분류표를 만듭니다. 독립 실습의 useSelection과 누적 TaskFlow의 taskStore는 서로 다른 연습 코드입니다. 이 단계는 기존 앱을 바꾸는 단계가 아니라 상태 소유자와 이후 이전 순서를 기록하는 단계입니다.
여기부터는 React 과정의 TaskFlow를 계속 발전시키는 누적 실습입니다.
이전 체크포인트
step-18-reducer-context
현재 상태
TaskFlow의 주요 작업 상태는 Context와 reducer를 통해 공유됩니다.
TaskProvider
├─ tasks
├─ taskReducer
└─ dispatch
이번 목표
코드를 크게 바꾸지 않고, 앞으로 Zustand로 옮길 상태와 남겨둘 상태를 분류합니다.
Before
Context 안에 여러 상태를 계속 추가할 가능성이 있음
상태의 성격을 구분하는 기준이 아직 없음
After
React 로컬 state
Zustand 클라이언트 전역 상태
TanStack Query 서버 상태
세 영역의 책임을 구분함
상태 분류표
TaskFlow에서 앞으로 다룰 상태를 다음처럼 분류합니다.
tasks
- 현재는 로컬 실습 데이터
- Zustand 이전 연습 가능
- 서버 데이터가 되면 TanStack Query로 이동
selectedTaskId
- 여러 컴포넌트가 공유
- Zustand 후보
isTaskModalOpen
- 여러 위치에서 제어 가능
- Zustand 후보
filter
- 목록과 필터 UI가 함께 사용
- Zustand 후보
formInput
- TaskForm 내부에서만 사용
- 로컬 state 유지 가능
현재 확장 단계
→ 1. Zustand 과정 전체 로드맵
○ 2. 상태 경계 결정
○ 3. Store 구조 설계
○ 4. Zustand 설치
○ 5. state 이전
○ 6. action 이전
○ 7. selector
○ 8~15. 최적화·persist·실무 구조
완료 체크포인트
zustand-step-01-roadmap
이번 단계에서는 실행 코드가 크게 달라지지 않습니다. 중요한 결과는 “무엇을 Zustand에 넣을지” 기준을 먼저 확보하는 것입니다.
다음 학습
다음 글에서는 React state, Context, Zustand를 실제 기준으로 비교하고 TaskFlow의 상태를 로컬 상태와 전역 상태로 나눕니다.
다음 글: React state vs Zustand: 전역 상태가 필요한 기준
공식 문서
- Zustand 공식 문서: https://zustand.docs.pmnd.rs/
- Zustand GitHub: https://github.com/pmndrs/zustand
글 소개·학습 준비·전체 목차

Zustand · 1 / 15 · 필수 길잡이 · 과정 안내
Zustand를 처음 공부할 때 가장 중요한 것은 API를 많이 외우는 것이 아니라, 어떤 상태를 전역 store로 옮겨야 하고 어떤 상태는 컴포넌트에 남겨야 하는지 기준을 잡는 것입니다.
처음에는 작은 state와 action을 가진 store를 만들고, selector로 구독 범위를 좁힌 다음 필요한 값에만 persist를 적용하세요. 이 순서라면 상태 소유권·변경 규칙·렌더 기준·저장 수명을 차례로 확인할 수 있습니다. 이전 프로젝트 없이 시작할 준비 단계와 선택 예제가 아래에 있습니다.
상단에서는 이 글만 찾아온 독자도 Zustand 학습 순서를 이해할 수 있게 설명하고, 하단에서는 React TaskFlow의 현재 상태를 점검해 Zustand 과정의 출발점을 만듭니다.
이번 글에서 배우는 내용
- Zustand가 해결하려는 문제
- React state, Context, Zustand의 역할 차이
- store → action → selector → persist 순서
- middleware와 devtools를 언제 도입하는지
- persist migration이 필요한 이유
- 서버 상태와 클라이언트 전역 상태를 구분하는 기준
- TaskFlow가 15회 동안 어떻게 바뀌는지
먼저 알아야 할 내용
이 과정은 React 기본 문법을 알고 있다는 전제에서 진행합니다.
최소한 다음 내용을 이해하면 좋습니다.
- useState
- props
- Context
- useReducer
- 컴포넌트 리렌더링
- TypeScript 기본 타입
누적 프로젝트를 이어갈 때의 시작 상태는 하단 프로젝트 영역에서 확인합니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 15개 · 필수 14개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.