프론트엔드 개발에서 상태 관리는 UI의 일관성과 유지보수성을 좌우하는 핵심 요소이다.
React는 다양한 방식의 상태 관리 도구를 제공하며, 각각의 방식은 사용 방식, 규모, 복잡도에 따라 적절하게 선택해야 한다.
상태 관리 라이브러리는 상태 관리를 편하게 만들어주는 도구이기 때문에, 배우는 입장에서는 그냥 노가다 박치기로 해보는 것도 나쁘지 않았을 것 같긴한데 ...
사실 나는 그런 경험없이 "요즘은 서버 상태 관리는 Tanstack Query, 클라이언트 상태 관리는 Zustand를 이용해서 하는 게 대세다."라는 얘기를 듣고 그것부터 시작했다.
여러 프로젝트를 하면서 Zustand와 Tanstack query를 써봤는데, 이번 기회에 한 번 정리해보는 걸로 !!
상태와 상태 관리
먼저 알고 가야 하는 상태(state)란 현재 입력값, 현재 이미지, 장바구니와 같이 기억해야 하는 컴포넌트별 메모리를 의미한다.
(참고: React 공식 문서 - State: 컴포넌트의 기억 저장소 https://ko.react.dev/learn/state-a-components-memory)
상태는 시간에 따라, 사용자와의 상호작용에 따라 업데이트되며, 이렇게 변화하는 상태를 앱의 다양한 부분이 서로 일관되게 공유하여 사용자의 상호작용에 올바르게 반응하게 해주는 관리를 상태 관리라고 한다.

React에서는 컴포넌트의 state와 props를 통해 상태 관리를 하는 것이 기본적이지만, 하나의 애플리케이션을 만들기 위해서는 여러 컴포넌트가 필요하다. 하지만, 여러 컴포넌트간에 상태를 공유할 때 일일이 props를 넘겨주게되면 상태 전달 경로가 복잡해지고, 유지보수가 상당히 어려워진다. (이를 prop drilling현상이라고 한다.)
이럴 때 상태 관리 도구를 사용한다면, 일일이 props를 전달하지 않고도 트리 전역에 데이터를 공급하고 복잡한 상태 변경 로직을 한 곳에서 관리할 수 있어 개발 효율이 높아진다.
상태의 종류
| 구분 | 설명 | 예시 |
| 로컬 상태 (클라이언트 상태) |
특정 컴포넌트 내부에서만 사용하는 값 | 모달 open 여부, input 값 |
| 전역 상태 | 여러 컴포넌트가 공유하는 클라이언트 상태 | 로그인 사용자 정보, 장바구니, 테마 |
| 서버 상태 | 외부 API에서 가져온 데이터와 관련된 상태 | 목록 조회 결과, 로딩/에러 상태 |
useContext
props drilling 없이 부모 → 하위 컴포넌트 트리 전반에 데이터를 전달할 수 있는 리액트 훅
Context

부모 컴포넌트가 그 아래의 트리 전체에 데이터를 전달할 수 있도록 해준다.
useContext
useContext를 컴포넌트의 최상위 수준에서 호출하여 Context를 읽고 구독할 수 있다.
const ThemeContext = React.createContext('light')
function App() {
return (
<ThemeContext.Provider value="dark">
<Main />
</ThemeContext.Provider>
)
}
function Main() {
const theme = React.useContext(ThemeContext)
return <h1>Theme: {theme}</h1>
}
지금 알았는데 나는 React의 기본 useContext조차 사용해본 적이 없었다 !
React 공식문서는 정말 자세하고 쉽게 설명이 되어있기 때문에, 한 번 따라하면서 공부해보는 것도 좋을 것 같다.
https://ko.react.dev/learn/passing-data-deeply-with-context#replace-prop-drilling-with-context
Context를 사용해 데이터를 깊게 전달하기 – React
The library for web and native user interfaces
ko.react.dev
Zustand
A small, fast, and scalable bearbones state management solution.
작고, 빠르며, 확장 가능한 최소 구성의 상태 관리 솔루션

https://zustand.docs.pmnd.rs/getting-started/introduction
Introduction - Zustand
How to use Zustand
zustand.docs.pmnd.rs
귀여운 곰돌이가 로고인 Zustand. 독일어로 '상태'를 뜻하는 단어가 zustand라고 한다.
React Hook기반으로 동작하며, 간단한 코드로 빠르게 전역 상태를 만들 수 있다.
장점
- 간단하다.
- 가볍다.
- 빠르다.
단점
- 복잡한 상태관리에는 한계가 있다.(고 한다. 나는 느끼지 못했지만)
- 커뮤니티가 상대적으로 작다. (Redux같은 대중적인 라이브러리에 비해..)
예제
// store/userStore.ts
import { create } from 'zustand'
type User = { id: string; name: string } | null
type State = {
user: User
setUser: (u: User) => void
clearUser: () => void
}
export const useUserStore = create<State>((set) => ({
user: null,
setUser: (u) => set({ user: u }),
clearUser: () => set({ user: null }),
}))
// 컴포넌트에서: 필요한 조각만 구독(리렌더 절약)
const user = useUserStore((s) => s.user)
const setUser = useUserStore((s) => s.setUser)
전역 상태 남발하지 않기
context는 전역 설정처럼 자주 바뀌지 않는 값을 공유하는 데에는 무리가 없어보이고, zustand와 같은 라이브러리를 사용하면 자주 바뀌는 전역 상태와 행동까지 함께 다룰 때 적합하다.
zustand를 남발하여 사용하지 않는 것도 중요하다. 전역 상태가 많아질수록 결합도가 높아지기 때문에, 컴포넌트 내부에서만 쓰는 일회상 상태는 useState나 useReducer로 해결하면 된다.
최근 클라이언트 상태 관리 라이브러리 트렌드

Zustand 이전에 많이 쓰이던 건 Redux와 Recoil이 있는데, npm trend를 비교해보면 아무래도 redux가 아직까지는 많이 쓰이고 있고, zustand가 격차를 조금 좁히고 있지 않나.. 하는 생각이 든다.
사용법이 어렵고 보일러 플레이트가 많다는 등의 이유로 redux는 욕을 많이 먹고.. 이를 해결하기 위해 redux-toolkit도 등장했다고 하는데, 그보다도 zustand가 더 사용하기 쉽다고 함.
하나하나 사용법을 비교하기엔 글이 길어질 것 같기도 하고 나도 잘 아는 건 아니여서 이 글에서 작성하지는 않겠지만, zustand가 확실히 사용이 쉬워보이기는 한다.
다만, zustand는 아직 한글로된 공식문서도 없어서 아쉽다. 나도 프로젝트 하면서 꽤나 힘들었고 짧은 기간내에 빨리 만들기 위해 공식문서보다는 생성형AI에게 더 많은 질문을 했다 ..
Tanstack Query
Powerful asynchronous state management, server-state utilities and data fetching
강력한 비동기 상태 관리, 서버 상태 유틸리티, 그리고 데이터 페칭 기능

https://tanstack.com/query/latest
TanStack Query
Powerful asynchronous state management, server-state utilities and data fetching. Fetch, cache, update, and wrangle all forms of async data in your TS/JS, React, Vue, Solid, Svelte & Angular applications all without touching any "global state"
tanstack.com
Tanstack Query는 서버 상태 관리 라이브러리로, 비동기 상태 관리와 쿼리 무효화 같은 기능이 정말 편리하다.
장점
- 비동기 데이터 처리 간소화
- 자동 캐싱 & 갱신
- 불필요한 요청 방지
- API 호출 로직을 UI 컴포넌트 밖으로 분리해 깔끔한 구조 유지 가능
단점
- 러닝 커브
TanStack Query의 생명주기와 주요 옵션
- staleTime: 데이터가 신선하다고 간주되는 시간
- cacheTime: 사용하지 않는 쿼리가 캐시에 머무는 시간
- refetchOnWindowFocus: 창 포커스 시 데이터 자동 갱신 여부
TanStack Query는 쿼리 요청 → 캐싱 → staleTime 동안 재사용 → 만료 시 refetch 순으로 동작하며, 이때 위 옵션들이 동작 방식을 제어한다.
이런 옵션들을 잘 활용하면, 데이터가 언제 갱신되고 언제 캐시에서 불러올지를 세밀하게 제어할 수 있어, 네트워크 효율과 사용자 경험 모두 잡을 수 있다.
최근 서버 상태 관리 라이브러리 트렌드

npm trends 상에서도 swr보다 더 .. 최근에는 훨씬 더 많이 사용되고 있음을 볼 수 있다.
자료도 많고 아주 굳.
많은 프로젝트를 해본 건 아니지만, 탠스택 쿼리를 써본 이후로는 fetch나 axios만으로 API 호출 로직을 직접 짜는 게 상상이 안 된다.
로딩·에러 상태 관리, 캐싱, 쿼리 무효화까지 한 번에 해결되니, 로컬 상태 중심의 단순한 서비스가 아니라면 탠스택 쿼리를 쓰지 않을 이유가 없다고 느껴진다.
정리
- 로컬 UI 상태만 있다 → React state/useReducer
- 간단한 전역 상태 필요 → Zustand
- 서버 데이터 관리 필요 → TanStack Query
- 둘 다 필요 → TanStack Query + Zustand 병행
사실 상태 관리 도구를 고를 때는 유행보다는 내 프로젝트의 데이터 흐름과 성격을 먼저 살펴보는 게 훨씬 중요하다.
데이터의 출처(로컬 vs 서버), 변경 주기, 동시성 필요 여부를 고려하면 불필요하게 무겁고 복잡한 툴을 들이는 실수를 줄일 수 있을 것이다.
그리고 어떤 라이브러리를 쓰느냐보다 더 중요한 건
처음부터 상태를 필요 이상으로 만들지 않는 것과 불필요한 리렌더링을 막는 최적화 노력일 것이다.