⚛️ 리액트 개발 환경 세팅: CRA vs Vite

⚛️ 리액트 개발 환경 세팅: CRA vs Vite

서버 상태 vs 클라이언트 상태: React Query와 Zustand로 완벽하게 분리하기

복잡한 리액트 상태 관리, 두 가지 개념만 알면 쉬워집니다.

혹시 리액트(React) 프로젝트를 진행하면서 "왜 내 리덕스(Redux) 스토어는 이렇게 비대해질까?"라는 고민을 해보신 적이 있으신가요?
API에서 가져온 데이터를 전역 상태 라이브러리에 쑤셔 넣고, 로딩 처리를 위해 `isLoading`, `isError` 같은 변수들을 일일이 만들다 보면 코드는 어느새 스파게티가 되어버립니다.
이 문제의 근본적인 원인은 바로 '서버 상태'와 '클라이언트 상태'를 구분하지 않았기 때문입니다.
오늘 이 글에서는 두 상태의 명확한 차이를 이해하고, 각각에 맞는 도구(React Query, Zustand)를 사용하여 우아하게 코드를 관리하는 방법을 알아보겠습니다.

🧩 1. 서버 상태와 클라이언트 상태, 도대체 뭐가 다를까?

상태 관리를 효율적으로 하려면 먼저 우리가 다루는 데이터의 성격을 파악해야 합니다.
개발 현장에서 흔히 혼동하는 두 가지 상태의 정의를 명확히 짚고 넘어가겠습니다.

📡 서버 상태 (Server State)

클라이언트가 제어하지 않는, 원격 위치에 저장된 데이터입니다.
비동기적이며, 다른 사람에 의해 변경될 수 있어 시점의 차이(Out of Date)가 발생할 수 있습니다.
예시: DB의 사용자 정보, 게시글 목록, 상품 재고 수량 등.

💻 클라이언트 상태 (Client State)

브라우저 세션 동안 사용자의 인터랙션에 의해 생성되고 관리되는 데이터입니다.
동기적이며, 항상 최신 상태를 유지하기 쉽습니다.
예시: 모달 열림/닫힘 여부, 다크 모드 토글, 폼 입력값, 현재 선택된 탭 등.

이 두 가지를 구분하지 않고 Redux나 Recoil 같은 전역 상태 라이브러리에 모두 넣는 순간, 비동기 데이터를 관리하기 위한 불필요한 보일러플레이트 코드가 폭증하게 됩니다.


🛠️ 2. 도구의 역할 분리: 적재적소의 원칙

각 상태의 특성을 이해했다면, 이제 그에 맞는 최적의 도구를 매칭해줄 차례입니다.
현대적인 리액트 생태계에서는 다음과 같은 조합이 국룰(Standard)로 자리 잡고 있습니다.

구분 추천 도구 주요 기능 및 장점
서버 상태 React Query
(TanStack Query)
- 데이터 캐싱 및 중복 제거
- 자동 재요청(Refetch)
- 로딩/에러 상태 자동 관리
클라이언트 상태 Zustand
또는 Redux
- 직관적인 UI 상태 제어
- 컴포넌트 간 데이터 공유
- 가볍고 빠른 설정 (Zustand)

👉 React Query가 서버 상태에 강한 이유

React Query는 "서버 데이터는 빌려온 것"이라는 철학을 가지고 있습니다.
개발자가 직접 `useEffect`로 데이터를 가져오고 상태에 저장하는 수고를 덜어주며, Stale-While-Revalidate 전략을 통해 사용자에게 항상 쾌적한 경험을 제공합니다.

👉 Zustand가 클라이언트 상태에 적합한 이유

반면 Zustand는 "작고 가볍게"를 지향합니다.
복잡한 Redux의 보일러플레이트 없이, 정말 필요한 UI 상태(예: 사이드바 열림 상태)만 전역으로 관리하기에 최적화되어 있습니다.


💻 3. 코드로 보는 변화 (Before & After)

백문이 불여일견, 실제 코드가 어떻게 간결해지는지 살펴보겠습니다.
기존에 Redux로 서버 데이터를 관리하던 방식과 분리 전략을 적용한 방식을 비교해 보세요.

❌ 기존 방식 (Redux Thunk 사용 시)

// 액션 타입 정의, 액션 생성함수, 리듀서, 청크 함수...
const fetchUser = createAsyncThunk(...)
...
// 컴포넌트 내부
useEffect(() => { dispatch(fetchUser()) }, [])
if (isLoading) return <Loader />
if (error) return <Error />

✅ 개선된 방식 (React Query 사용)

// 단 한 줄로 해결
const { data, isLoading, error } = useQuery('user', fetchUser);

if (isLoading) return <Loader />
// 캐싱, 리패칭 등은 React Query가 알아서 처리함

보시는 것처럼 서버 상태 관리는 React Query에 위임함으로써 코드가 획기적으로 줄어듭니다.
이제 Zustand 스토어에는 순수한 UI 상태만 남기게 되어 유지보수성이 극대화됩니다.


📌 요약 및 실천 가이드

오늘 다룬 내용의 핵심은 "데이터의 주인(Owner)을 찾아주는 것"입니다.

  • 서버 상태 (API 데이터): React Query에게 맡기세요. 캐싱과 동기화를 책임집니다.
  • 클라이언트 상태 (UI 제어): Zustand (혹은 Redux, Recoil)에게 맡기세요. 컴포넌트 간 공유를 책임집니다.
  • 이 둘을 섞지 않고 분리하는 것이 모던 리액트 개발의 지름길입니다.

지금 당장 여러분의 프로젝트 코드를 열어보세요.
혹시 Redux 스토어 안에 `serverData` 같은 객체가 들어있지는 않나요?
작은 컴포넌트부터 하나씩 React Query로 마이그레이션 해보는 것을 추천합니다.

댓글 쓰기