0) Intro
- React에서 상태가 늘어날수록 생기는 문제:
- 여기저기서 필요한 로그인 정보, 모달 open 여부, 알림 개수…
- props drilling, 중복 상태, 관리 지옥.
- 목표:
- “전역 상태에만 의존하지말고, 필요한 곳에만, 맞는 도구를 쓰자”
1) 상태의 종류 정리
- Local State (지역 상태)
- input 값, 모달 열림 여부 등 컴포넌트 내부에서만 쓰는 것
- Global(Client) State (전역 클라이언트 상태)
- 로그인 유저, 테마, 다크모드, 장바구니 처럼 여러 페이지/컴포넌트에서 같이 쓰는 값
- Server State (서버 상태)
- API에서 가져오는 데이터 (상품 리스트, 댓글 등)
- 캐싱/동기화는 TanStack Query 가 담당하는 게 좋음
2) 전역 상태 라이브러리가 필요한 순간
- 같은 상태를 N군데에서 읽고/수정해야 한다.
- props로 계속 넘기다 보니 단계가 3단계 이상이다.
- Context만 쓰면 파일이 너무 비대해지거나, 리렌더링이 많다.
- 특정 상태를 어디서 관리하는지 헷갈리기 시작했다.
- 디버깅으로 분석과정이 필요하다
이 중 2~3개 이상이면 전역 상태 라이브러리 고려해봐야함
3) 라이브러리 4종 정리
3-1. Redux :중앙집중식 저장소 store를 두고 화면에 필요한 전역상태를 store에 모아두고 관리한다. stor, action,reducer로 구성되어있다.
- 사용: store 만들고 slice 쪼개고 action 정의하고... 할 게 좀 많다.
- 장점
- 1. 중앙화된 상태관리
- 2. 상태 관리 로직 분리
- 3. 데이터의 일관성을 유지할 수 있다. ssot(single source of truth 상태가 한곳에서만 정의)
- 단점: 무거워서 작은 규모에서는 필요없다. 러닝커브가 높다
// store/counterSlice.js
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
inc: (state) => { state.value += 1; },
dec: (state) => { state.value -= 1; },
},
});
export const { inc, dec } = counterSlice.actions;
export const store = configureStore({
reducer: { counter: counterSlice.reducer },
});
3-2. Zustand
- 라이브러리의 크기가 작다 (1kb 번들기준으로 안팎이다. 그래서 가벼워서 요새 굉장히 많이 쓰임
- 상태 변경시 불필요한 리렌더링 제어(각 컴포넌트에서 필요한 조각만 선택해서 구독함, 더 세밀하게 제어할 수 있다
- 타입스크립트기반, 미들웨어 지원
- 두개 이상의 컴포넌트가 같은 가변성 데이터 소스를 사용할 때 어떤 컴포넌트는 이전값 , 다른 컴포넌트는 변경된 값을 보여주는 일이 발생할 수 있음. zustand는 전역스토어와 리액트18과 호환되는 구독 방식 덕분에 이러한 불일치 문제를 줄여줌
- 사용 : create 함수 하나만 쓰면 됨
- 장점: 코드가 짧고 직관적이다. Selector 쓰면 리렌더링 제어도 쉽다.
- 단점: 너무 자유로워서 규칙 없이 막 쓰면 나중에 큰일남
// store/useTodoStore.js
import { create } from "zustand";
export const useTodoStore = create((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: Date.now(), text, done: false }],
})),
}));
3-3. Jotai
- 특징: 상태의 최소 단위 (Atom) 를 중심으로 관리하는 경량 상태관리 라이브러리
- 사용 : const countAtom = atom(0) 선언하고, 필요한 컴포넌트에서 바로 갖다 쓴다.
- 장점: 진짜 필요한 컴포넌트만 딱 리렌더링 된다. 조합해서 쓰기 좋다
- 단점: 개념(파생 atom 등) 공부해야함.
import { atom, useAtom } from "jotai";
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<button onClick={() => setCount((c) => c + 1)}>
{count}
</button>
);
}
3-4. Recoil (리코이는 더이상 유지보수가 되지 않아서 이제 사용하지 않는다. )
- 특징: Jotai랑 비슷하게 atom/selector, React 친화적.
- 장점: 비동기 처리가 편하고, 리액트스럽게 코드를 짤 수 있다.
- 단점: 업데이트가 좀 뜸하고 Redux만큼 생태계가 크진 않다
import { atom, useRecoilState } from "recoil";
const textState = atom({
key: "textState",
default: "",
});
function Input() {
const [text, setText] = useRecoilState(textState);
return (
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
);
}
4) 진짜 써야 할까?
1. 작은 사이드 프로젝트에 Redux(Toolkit)가 필요한가?
결론: 90% 이상 필요하지 않다에 가깝습니다
.
- 비용 > 효용: 작은 프로젝트에서는 전역 상태의 수 자체가 적고 로직이 단순합니다. Redux Toolkit이 제공하는 패턴(Store, Slice, Reducer, Action)을 모두 설정하는 초기 설정 비용이 실제 구현하려는 기능의 복잡도보다 큽니다.
- 대안: useState, useReducer 혹은 Context API만으로도 충분히 관리가 가능합니다.
- 예외: 만약 이 '작은' 프로젝트가 향후 지속적으로 유지보수되고 기능이 확장될 계획이 있다면, 초기부터 구조를 잡는 차원에서 Redux Toolkit 도입을 고려해볼 수 있다.
2. TanStack Query + Context API만으로도 해결 가능한데 굳이?
결론: 매우 합리적인 조합이며, 대부분의 경우 이것만으로도 충분
이 질문의 핵심은 "서버 상태"와 "클라이언트 상태"의 분리입니다.
- TanStack Query (서버 상태): API 호출, 캐싱, 데이터 동기화, 로딩/에러 상태 관리 등 '서버에서 가져오는 데이터'는 이 라이브러리가 전담하는 것이 옳습니다.
- Context API (클라이언트 상태): 테마(다크 모드), 언어 설정, 모달 열림 여부, 사용자 인증 정보 등 '앱 내부의 UI/설정 상태'를 관리하기에 적합합니다.
전역 상태 라이브러리가 필요한 순간은 다음과 같습니다.
- Context가 너무 많아질 때: UserContext, ThemeContext, ModalContext 등 Context가 여러 개로 분산되어 관리가 파편화될 때.
- Context가 너무 비대해질 때: 하나의 Context에 너무 많은 상태를 담아, 관련 없는 컴포넌트까지 불필요한 리렌더링이 발생하는 성능 문제가 생길 때.
- 복잡한 클라이언트 상태 로직: 여러 단계의 폼, 드래프트(임시 저장) 상태 등 단순 UI 상태를 넘어선 복잡한 '도메인 상태'가 필요할 때.
이런 경우, 서버 상태는 TanStack Query에 맡기고 복잡해진 '클라이언트 상태'만 Zustand나 Jotai 같은 가벼운 라이브러리로 옮기는 것이 효율적입니다.
3. "전역 상태 라이브러리 = 마지막 선택지"에 동의하는가?
결론: "한 번 도입하면 되돌리기 어려운 무거운 것"
- 전염성: 일단 도입하면, store를 import하는 코드가 프로젝트 전반으로 퍼져나가며 라이브러리에 대한 의존도가 급격히 높아집니다.
- 교체 비용: 나중에 "이건 Context로 충분했네"라고 깨달아도, 이미 의존하는 수많은 컴포넌트 때문에 구조를 다시 되돌리기 어렵습니다.
- 문제 전이: 설계를 잘못하면 Props Drilling 지옥이 해결되는 것이 아니라 오히려 추적 불가능한 전역 상태가 되어버린다.
따라서 다음과 같이 단계적으로 상태관리에 접근하는게 좋다.
Local State (useState) → Context API (간단한 전역) → Server State (TanStack Query) → (그래도 안되면) → Global State Library
4. 팀 프로젝트: 규칙 없는 Zustand vs 패턴 통일 Redux Toolkit
결론: 팀의 규모와 프로젝트의 수명에 따라 다름.
> "규칙과 함께 쓰는 Zustand" (자율성/속도)
- 적합: 2~5명 소규모 팀, 해커톤, MVP 만들 때 추천.
- 장점: API가 단순해 개발 속도가 빠르다.
- 필수 조건: 최소한의 규칙은 반드시 필요
> "패턴을 통일하는 Redux Toolkit" (일관성/안정성)
- 적합: 팀원이 많거나, 프로젝트를 1년 이상 길게 가져가야 한다면 추천 -> 유지보수에 좋음
- 장점: Slice, Reducer 등 명확한 패턴을 요구하여 누가 작업해도 비슷한 구조의 코드가 나온다.
최종 요약
상태 관리는 가장 멋진 도구를 쓰는 것이 아니라, 가장 최소한의 도구로 시작해야 합니다.
서버 상태는 TanStack Query에 맡기고, 클라이언트 상태는 Local State와 Context API로 우선 해결하기.
전역 상태 라이브러리는 이 두 가지로 해결이 안 되는 복잡한 상황이 생겼을 때 , 그 문제를 해결하기 위해 신중하게 선택하는 도구입니다.

'FE' 카테고리의 다른 글
| TanStack Query 낙관적 업데이트: onMutate + Rollback을 찜 해제에 적용 (0) | 2025.12.19 |
|---|---|
| [멋사] 2025 4호선톤 회고 (0) | 2025.11.17 |
| [Javascript] 웹 애니메이션 , requestAnimationFrame (0) | 2025.11.06 |
| 레이아웃 스래싱(Layout Thrashing) (0) | 2025.10.26 |
| 자바스크립트 기초: 객체, DOM, 이벤트 핸들링 (0) | 2025.10.18 |