Zustand 학습 로드맵: store·action·selector·persist 순서

2026.05.12·수정 2026.09.14·약 15분·작성: 해비·블로그 소개

학습 목표·선수 지식: 단계별 완료 기준으로 읽기 순서를 정하고 로컬 UI·공유 UI·URL·서버 데이터의 경계를 설명합니다. 선수 지식: React 컴포넌트, useState, TypeScript 객체 타입.

Zustand는 이 순서로 익히면 됩니다

먼저 로컬 상태와 공유 상태의 경계를 정하고, TypeScript로 작은 store를 만든 뒤 action과 원자 selector를 익힙니다. 여러 값을 한 번에 선택할 때만 useShallow를 검토하고, 저장할 이유가 분명한 상태에만 마지막 단계로 persist를 붙입니다. Next.js에서는 요청 간 store 공유와 hydration까지 별도 문제로 다뤄야 합니다.

검증 기준: 2026년 9월 12일 확인한 Zustand 공식 문서와 Next.js 가이드입니다. 예제는 React·TypeScript를 기준으로 하며 설치한 Zustand 버전의 API를 함께 확인하세요.

Zustand store action selector persist 학습 경로를 단계별로 보여주는 다이어그램

0단계: 무엇을 store에 넣을지 먼저 정한다

아래는 실제 공개 글을 기준으로 한 전체 읽기 순서입니다. 이 로드맵은 출발 안내이고, store 생성·selector·저장 동작은 연결한 개별 글에서 실습합니다. 참고 글은 관련 문제를 만났을 때 재방문해도 됩니다.

이 로드맵이 읽기 순서 1번이며, 아래 연결 글은 2번부터 이어집니다.

읽기 순서 공개 글 역할
2 React state vs Zustand: 전역 상태가 필요한 기준 길잡이
3 Zustand란? React 상태 관리 선택 기준과 기본 Store 학습
4 Zustand 설치 사용법: 기본 Store 만들고 상태 연결하기 학습
5 Zustand state 사용법: 값 읽기와 변경 흐름 익히기 학습
6 Zustand action 사용법: 상태 변경 로직을 store로 분리하기 학습
7 Zustand selector 사용법: 필요한 상태만 가져와 리렌더링 줄이기 학습
8 Zustand 리렌더링 원리와 selector 최적화 방법 학습
9 Zustand persist 사용법: 새로고침 후 상태 저장하기 학습
10 Zustand persist 마이그레이션 기준: 저장된 상태 구조가 바뀔 때 학습
11 Zustand 상태 변경 후 리렌더링이 안 될 때 해결 방법 참고
12 Zustand 실무 사용 기준: store가 복잡해질 때 피할 실수 참고

Zustand 문법부터 외우면 입력값, hover, 서버 응답까지 전부 전역 store에 넣기 쉽습니다. 학습의 시작점은 create가 아니라 상태의 소유 범위입니다. 한 컴포넌트나 가까운 부모·자식 안에서만 쓰는 값은 useState가 더 단순할 수 있습니다.

상태 예시 첫 선택 store를 검토할 신호
입력 중인 검색어, 단일 모달 useState 서로 먼 화면이 같은 값을 읽고 변경함
테마, 장바구니, 여러 영역의 필터 공유 위치에 따라 Context 또는 Zustand Provider 계층과 변경 로직이 복잡해짐
API 목록, 캐시, 재시도 상태 요구사항에 맞는 data fetching 계층 Zustand로 직접 캐시를 설계할 명확한 이유가 있음
URL로 공유해야 하는 필터 route 또는 search params URL과 store를 동기화할 책임을 감수할 수 있음

판단이 아직 어렵다면 Zustand란 무엇인가에서 props, Context, Zustand, 서버 상태의 선택 기준을 먼저 확인하세요.

1단계: TypeScript store를 작게 만든다

TypeScript에서는 공식 문서가 안내하는 커리드 형태 create<State>()(...)를 기본으로 두면 state와 action의 형태를 명시적으로 검사할 수 있습니다. 처음부터 모든 앱 상태를 하나로 합치지 말고 기능 경계가 분명한 작은 store로 시작하세요.

store에 state와 action 타입을 함께 정의합니다. 기본값과 변경 함수가 분리되어 읽히는지 다음 설치 실습에서 확인합니다. Zustand 설치 사용법: 기본 Store 만들고 상태 연결하기

Zustand의 set은 기본적으로 state 객체를 얕게 병합합니다. 중첩 객체를 바꿀 때는 내부 필드가 자동으로 깊게 합쳐진다고 생각하지 말고 새 객체를 명시적으로 만드세요. 전체 state를 교체하는 두 번째 인자 true는 action까지 지울 수 있으므로 필요한 경우에만 조심해서 사용합니다.

2단계: action으로 변경 규칙을 모은다

action은 Zustand가 강제하는 특별 문법이 아니라 store에 함께 둔 함수입니다. 그러나 컴포넌트마다 setState로 서로 다른 객체를 만들기 시작하면 같은 기능의 규칙이 흩어집니다. “카테고리를 바꾸면 페이지를 1로 초기화한다” 같은 도메인 규칙은 하나의 action으로 묶는 편이 추적하기 쉽습니다.

필터 실습은 setKeyword·setCategory·toggleSoldOut·resetFilters를 구현합니다. 두 번 토글하면 원래 값으로 돌아가고 검색어와 카테고리 부분 변경이 서로를 보존하는지 확인합니다. Zustand action 사용법: 상태 변경 로직을 store로 분리하기

이전 값으로 다음 값을 계산할 때는 set((state) => …) updater를 사용합니다. 연결된 필터 실습에서 컴포넌트는 toggleSoldOut과 resetFilters를 호출하고 변경 규칙은 store에 둡니다.

3단계: 필요한 값만 selector로 구독한다

useProductFilterStore()처럼 selector 없이 전체 store를 읽으면 그 컴포넌트는 store 전체를 구독합니다. 작은 예제에서는 편하지만 store가 커질수록 관련 없는 변경에도 다시 렌더링될 수 있습니다. 원자 selector를 기본으로 두면 컴포넌트의 의존성이 코드에 드러납니다.

SearchInput은 keyword와 setKeyword를 각각 선택합니다. 다른 필드가 바뀌어도 선택 결과가 같은지 확인합니다. Zustand selector 사용법: 필요한 상태만 가져와 리렌더링 줄이기

여러 값을 객체나 배열 하나로 묶어 선택하면 selector가 실행될 때마다 새 참조가 생길 수 있습니다. 선택 결과의 최상위 값이 같을 때 렌더링을 건너뛰고 싶다면 useShallow를 적용합니다. 무조건 모든 selector에 붙이지 말고 여러 pick을 묶어 반환하는 경우에 검토하세요.

FilterSummary는 useShallow로 최상위 선택 결과를 안정화합니다. 매번 새 객체를 그대로 반환하는 selector는 Zustand 5에서 무한 업데이트 오류가 날 수 있습니다. Zustand selector 사용법: 필요한 상태만 가져와 리렌더링 줄이기

리렌더링 원인을 더 자세히 확인하려면 Zustand 리렌더링과 selector에서 전체 구독, 새 객체 반환, 얕은 비교를 구분해 보세요.

4단계: persist는 저장할 상태에만 붙인다

읽기 경계도 따로 필요합니다. partialize는 앞으로 쓸 필드를 고르는 옵션입니다. 같은 버전의 저장값에는 migrate가 호출되지 않으므로, merge에서 unknown을 검사하고 허용 필드만 읽어야 합니다. 타입 단언만으로 과거 JSON을 정상 데이터로 만들 수 없습니다.

persist는 새로고침이나 앱 재시작 뒤 상태를 복원하는 middleware입니다. store 구조를 자동으로 좋게 만드는 기능이 아닙니다. 테마와 목록 표시 방식처럼 다시 방문해도 유지할 값만 partialize로 골라 저장하고, 모달 열림·요청 진행·서버 응답 캐시·민감한 인증값은 관성적으로 저장하지 마세요.

Zustand persist 적용 전 저장할 상태와 저장하지 않을 상태를 구분하는 구조 다이어그램

장바구니 저장 실습에서 쓰기 partialize와 읽기 merge를 따로 적용합니다. items와 selectedCouponId만 저장하고 패널 열림과 action은 저장 데이터에서 제외합니다. Zustand persist 사용법: 새로고침 후 상태 저장하기

version은 저장 구조의 버전이고, 저장된 버전이 현재와 다를 때 migrate가 최신 구조를 반환합니다. 필드명을 바꾸거나 타입을 변경했는데 version과 migrate를 두지 않으면 오래된 브라우저 저장값 때문에 특정 사용자에게만 오류가 날 수 있습니다. 자세한 옵션은 공식 persist 문서에서 partialize, version, migrate, skipHydration을 함께 확인하세요.

5단계: 저장과 hydration은 같은 문제가 아니다

브라우저 storage에서 복원한 값과 서버가 만든 초기 HTML의 값이 다르면 hydration 경고나 화면 깜빡임이 생길 수 있습니다. 예를 들어 서버는 light theme로 렌더링했는데 첫 클라이언트 렌더에서 localStorage의 dark theme를 즉시 읽으면 결과가 달라집니다.

해결책은 앱 요구사항에 따라 다릅니다. 서버가 같은 초기값을 전달해 서버·클라이언트가 일치하게 만들거나, 저장값 복원이 끝난 뒤 해당 UI를 표시하거나, SSR이 필요 없는 일부 UI만 클라이언트에서 렌더링할 수 있습니다. skipHydration: truestore.persist.rehydrate()로 복원 시점을 직접 제어할 수도 있지만 로딩 상태와 접근성을 함께 설계해야 합니다.

복원 예제는 오류 callback으로 실패를 구분하고 skipHydration과 rehydrate 호출을 연결합니다. 준비 UI는 복원 시도 종료를 나타내며 성공 여부와 다릅니다. Zustand persist 마이그레이션 기준: 저장된 상태 구조가 바뀔 때

skipHydration을 선택하면 클라이언트에서 rehydrate 호출과 실패 표시까지 연결해야 합니다. 모든 화면을 복원 뒤까지 숨길 필요는 없으며 저장값이 필요한 UI 범위만 설계합니다.

6단계: Next.js에서는 요청별 store와 RSC 경계를 지킨다

Zustand store는 module state가 될 수 있습니다. Next.js 서버는 여러 요청을 동시에 처리하므로 서버 모듈의 단일 store를 모든 사용자와 공유하면 요청 간 상태가 섞일 수 있습니다. 공식 Next.js 가이드는 store를 요청별로 만들고 React Server Component가 store를 읽거나 쓰지 않도록 권장합니다. RSC는 hooks나 Context를 쓰지 않으며 사용자별 클라이언트 상태를 보관하는 곳이 아닙니다.

서버에서 singleton을 만들지 않고 요청별 factory가 필요합니다. Next.js 통합 코드는 공식 가이드에서 Provider의 생성 범위와 첫 값을 함께 확인합니다. Zustand 실무 사용 기준: store가 복잡해질 때 피할 실수

Provider 수명과 route 수명을 맞춥니다. 이 로드맵은 실행 앱이 아니라 각 실습을 고르는 안내이며 RSC·동시 요청·브라우저 재수화 검증을 대신하지 않습니다. Zustand 실무 사용 기준: store가 복잡해질 때 피할 실수

Provider의 범위는 상태 수명과 맞춰야 합니다. route 이동에도 유지할 상태는 공통 layout 아래에, 특정 화면을 떠날 때 초기화할 상태는 해당 화면 경계에 둡니다. 서버에서 계산한 초기값을 넘긴다면 서버 HTML과 클라이언트 첫 상태가 같도록 직렬화 가능한 값만 전달하세요.

실무 적용 전 점검표

각 단계에서 남길 결과물

단계 다음으로 넘어갈 확인 기준
경계 검색창 입력 초안은 로컬, 적용된 검색어는 URL, 상품 목록은 서버 데이터라고 이유를 설명합니다.
store·action 패널과 배지가 함께 +1/-1/초기화를 반영하고 이전 객체는 변하지 않습니다.
selector 테마만 바꿨을 때 검색어 전용 구독은 갱신되지 않고 검색어를 바꾸면 갱신됩니다.
persist 새 store에 저장값을 복원하고 과거 UI 필드는 읽기 merge에서 제외합니다.
migration 구버전뿐 아니라 같은 버전의 null과 잘못된 중첩 객체도 검증합니다.
SSR 동시 요청의 사용자 값이 섞이지 않고 서버 HTML과 첫 클라이언트 상태가 같습니다. 이 로드맵의 실행 검증에는 SSR 앱이 포함되지 않습니다.
  1. 이 값이 정말 여러 컴포넌트에서 공유되는지 확인합니다.
  2. store가 사용자 입력·UI 상태와 서버 데이터 캐시를 무분별하게 섞지 않는지 확인합니다.
  3. TypeScript store는 create<State>()(...) 형태로 state와 action을 검사합니다.
  4. 컴포넌트는 전체 store 대신 필요한 값을 selector로 구독합니다.
  5. 객체·배열 pick을 묶을 때만 useShallow 필요성을 검토합니다.
  6. persist에는 저장할 필드만 partialize하고 schema 변경 시 version·migrate를 추가합니다.
  7. SSR HTML과 브라우저 저장값이 다른 경우 hydration 전략을 테스트합니다.
  8. Next.js 서버 module-global store를 만들지 않고 RSC에서 store를 읽거나 쓰지 않습니다.
  9. 새로고침, 로그아웃, 다중 탭, 저장값 구버전, route 이동을 테스트합니다.

공식 문서

이 순서를 한 번 적용한 뒤에는 Zustand persist 사용법Zustand 실무 주의사항에서 저장·분리 기준을 더 구체적으로 점검할 수 있습니다.

공식 근거와 확인 범위

확인일: 2026-09-12. 실행 프로젝트는 Zustand 5.0.15, React 19.3.0, TypeScript 7.0.2로 검사했습니다. 공식 문서의 API 설명과 실제 설치 버전의 동작을 구분해 확인합니다.

연결 학습: 미들웨어에서 Todo까지의 통과 기준

목표·선행 지식: 기본 store → action → selector → 미들웨어 → 로컬 Todo로 이어갑니다.

combine은 초기 상태와 추가 로직을 묶어 타입 추론을 돕습니다. immer는 draft 기반 변경, subscribeWithSelector는 선택 구독, devtools는 변경 관찰, persist는 저장소 복원을 담당합니다. 서로 다른 역할이므로 한 번에 모두 넣어야 하는 필수 묶음으로 외우지 않습니다.

직접 확인할 과제

빈 배열 타입을 명시하고 추가·토글·삭제·복원을 구현하세요. 빈 입력 거부, 이전 객체 보존, 구독 해제, 복원 후 action 유지까지 설명하면 서버 상태 학습으로 넘어갑니다.

공통 실습 ZIP · 다음 학습 · 라이브러리 선택 가이드

공통 ZIP은 버전을 고정한 학습 예제입니다. UI 파일은 Radix·Sonner·Embla 기반 축약 구현이며 shadcn CLI 생성물과 동일하지 않습니다. 적용 범위와 실행 방법은 ZIP의 README를 확인하세요.

이 글이 도움이 되었나요?

조회 중

Zustand 학습 순서

필수 14개 · 전체 15개

읽음 기록 관리

전체 과정 목차 (15개)
  1. 필수 길잡이 · Zustand 학습 로드맵: store·action·selector·persist 순서 현재 글
  2. 필수 길잡이 · React state vs Zustand: 전역 상태가 필요한 기준
  3. 필수 학습 · Zustand란? React 상태 관리 선택 기준과 기본 Store
  4. 필수 학습 · Zustand 설치 사용법: 기본 Store 만들고 상태 연결하기
  5. 필수 학습 · Zustand state 사용법: 값 읽기와 변경 흐름 익히기
  6. 필수 학습 · Zustand action 사용법: 상태 변경 로직을 store로 분리하기
  7. 필수 학습 · Zustand selector 사용법: 필요한 상태만 가져와 리렌더링 줄이기
  8. 필수 학습 · Zustand 리렌더링 원리와 selector 최적화 방법
  9. 필수 학습 · Zustand persist 사용법: 새로고침 후 상태 저장하기
  10. 필수 학습 · Zustand persist 마이그레이션 기준: 저장된 상태 구조가 바뀔 때
  11. 선택 참고 · Zustand 상태 변경 후 리렌더링이 안 될 때 해결 방법
  12. 필수 선수 · Zustand 실무 사용 기준: store가 복잡해질 때 피할 실수
  13. 필수 학습 · Zustand combine·immer 실습: 타입 추론과 중첩 상태 불변성
  14. 필수 학습 · Zustand subscribeWithSelector·devtools: 선택 구독과 해제 실습
  15. 필수 학습 · Zustand Todo 완성 실습: actions·선택 훅·persist 연결

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기