
최신 전역 상태 관리 라이브러리들과 리액트 훅의 철학 비교 분석
핵심 요약
TanStack Query와 Zustand는 React Hooks의 조합(composition) 철학을 완전히 수용하면서도, 각각 서버 상태와 클라이언트 상태라는 특화된 영역에서 React의 한계를 보완한다. 이들은 React의 함수형 프로그래밍 패러다임을 확장하되, 복잡성을 추가하지 않고 선언적이고 조합 가능한 API를 제공한다는 점에서 React Hooks와 철학적 일관성을 유지한다.
1. React Hooks의 근본 철학
1.1 조합성(Composition)과 재사용성
React Hooks는 **“작은 함수들을 조합하여 복잡한 로직을 구성”**하는 함수형 프로그래밍 철학을 따른다123:
// React의 조합 철학
function useUserProfile(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// 관련 로직이 한 곳에 모임
setLoading(true);
fetchUser(userId)
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading };
}
1.2 선언적(Declarative) 상태 관리
React는 **“무엇을 원하는지 선언하면, 어떻게 할지는 프레임워크가 처리”**하는 선언적 패러다임을 지향한다24.
1.3 관심사 분리(Separation of Concerns)
각 훅은 단일 책임을 가지며, 서로 다른 관심사를 명확히 분리한다15.
2. TanStack Query: 서버 상태의 전문가
2.1 철학적 특화: 서버 상태 vs 클라이언트 상태 구분
TanStack Query의 핵심 철학은 **“서버 상태는 클라이언트 상태와 본질적으로 다르다”**는 인식이다678:
서버 상태의 특성:
- 원격지에 저장되어 직접 제어 불가
- 비동기적이고 예측 불가능한 업데이트
- 캐싱, 동기화, 만료 등 복잡한 생명주기
- 네트워크 오류, 지연 등 불확실성
클라이언트 상태의 특성:
- 애플리케이션 내부에서 완전 제어 가능
- 동기적이고 예측 가능한 업데이트
- 단순한 읽기/쓰기 패턴
2.2 React Hooks와의 철학적 일관성
1) 선언적 API
// 명령형이 아닌 선언형 접근
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // "5분간 신선하게 유지하고 싶다"
});
// React가 처리하는 것들:
// - 캐싱, 백그라운드 업데이트, 중복 요청 제거
// - 네트워크 상태 추적, 자동 재시도, 오류 처리
2) 조합 가능한 커스텀 훅
function useUserProfile(userId) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
select: (user) => ({
name: user.name,
avatar: user.avatar,
isVerified: user.status === 'verified'
}),
});
}
function useUserPosts(userId) {
return useQuery({
queryKey: ['posts', userId],
queryFn: () => fetchUserPosts(userId),
enabled: !!userId, // userId가 있을 때만 실행
});
}
// 조합하여 사용
function UserDashboard({ userId }) {
const { data: user } = useUserProfile(userId);
const { data: posts } = useUserPosts(userId);
return (
<div>
<UserCard user={user} />
<PostList posts={posts} />
</div>
);
}
2.3 React 철학의 확장, 대체 아님
TanStack Query는 React를 대체하지 않고 서버 상태 영역에서 React의 철학을 확장한다68:
// React의 로컬 상태와 조화
function PostEditor() {
// 클라이언트 상태: React가 처리
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
// 서버 상태: TanStack Query가 처리
const { mutate: savePost, isLoading } = useMutation({
mutationFn: (postData) => savePost(postData),
onSuccess: () => {
// 성공 후 클라이언트 상태 초기화
setTitle('');
setContent('');
}
});
return (
<form onSubmit={() => savePost({ title, content })}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<textarea value={content} onChange={(e) => setContent(e.target.value)} />
<button disabled={isLoading}>Save</button>
</form>
);
}
3. Zustand: 최소주의 전역 상태 관리
3.1 철학: “React의 방식을 유지하면서 전역으로”
Zustand의 철학은 **“React스럽게, 하지만 전역적으로”**이다91011:
// Zustand 스토어 - 함수형, 불변성 기반
const useBearStore = create((set, get) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.Bears + 1 })),
decrease: () => set((state) => ({ bears: state.bears - 1 })),
// 조합 가능한 액션
reset: () => set({ bears: 0 }),
addMany: (count) => set((state) => ({ bears: state.bears + count })),
}));
// React 컴포넌트에서 사용 - 훅과 동일한 패턴
function BearCounter() {
const bears = useBearStore((state) => state.bears);
const increase = useBearStore((state) => state.increase);
return (
<div>
<span>{bears} bears</span>
<button onClick={increase}>Add bear</button>
</div>
);
}
3.2 React Hooks와의 철학적 공통점
1) 최소한의 보일러플레이트
// Redux의 복잡성 vs Zustand의 단순함
// Redux 방식
const INCREMENT = 'INCREMENT';
const increment = () => ({ type: INCREMENT });
const counterReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + 1 };
default:
return state;
}
};
// Zustand 방식 (React hooks와 유사한 단순함)
const useCountStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
2) 선택적 구독 (Selective Subscription)
// 성능 최적화를 위한 세밀한 구독 - React.memo와 유사한 철학
const Component1 = () => {
// bears만 변경될 때만 리렌더링
const bears = useBearStore((state) => state.bears);
return <div>{bears}</div>;
};
const Component2 = () => {
// actions만 가져오기 (리렌더링 없음)
const increase = useBearStore((state) => state.increase);
return <button onClick={increase}>+</button>;
};
3.3 커스텀 훅과의 조합
// Zustand + 커스텀 훅 패턴
function useAuth() {
const user = useAuthStore((state) => state.user);
const login = useAuthStore((state) => state.login);
const logout = useAuthStore((state) => state.logout);
const isAuthenticated = useMemo(() => !!user, [user]);
return { user, login, logout, isAuthenticated };
}
// React 컴포넌트에서 사용
function Header() {
const { user, logout, isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <LoginButton />;
}
return (
<div>
Welcome, {user.name}!
<button onClick={logout}>Logout</button>
</div>
);
}
4. 철학적 차이점과 상호 보완성
4.1 React Hooks의 한계와 외부 라이브러리의 보완
영역 | React Hooks | TanStack Query | Zustand |
---|---|---|---|
로컬 상태 | ✅ 완벽 (useState , useReducer ) | ❌ 관심사 밖 | ❌ 관심사 밖 |
서버 상태 | ⚠️ 수동 구현 필요 | ✅ 전문 특화 | ❌ 부적합 |
전역 클라이언트 상태 | ⚠️ Context의 한계 | ❌ 관심사 밖 | ✅ 최적화된 솔루션 |
컴포넌트 간 통신 | ⚠️ prop drilling | ❌ 해당 없음 | ✅ 직접 접근 |
4.2 실제 조합 사용 예시
// 1. TanStack Query: 서버 상태
const { data: todos } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
// 2. Zustand: UI 상태
const filter = useTodoStore((state) => state.filter);
const setFilter = useTodoStore((state) => state.setFilter);
// 3. React hooks: 로컬 상태
const [newTodo, setNewTodo] = useState('');
// 4. 조합된 로직
const filteredTodos = useMemo(() => {
if (!todos) return [];
return todos.filter(todo => {
if (filter === 'completed') return todo.completed;
if (filter === 'active') return !todo.completed;
return true;
});
}, [todos, filter]);
5. 철학적 일관성의 증거
5.1 공통된 설계 원칙
1) 함수형 프로그래밍 지향
- React: 순수 함수형 컴포넌트, 불변성
- TanStack Query: 순수한 쿼리 함수, 불변 캐시
- Zustand: 불변 상태 업데이트, 함수형 액션
2) 조합성 (Composability)
- React: 커스텀 훅으로 로직 조합
- TanStack Query: 쿼리 조합, 의존성 체인
- Zustand: 스토어 조합, 슬라이스 패턴
3) 예측 가능성 (Predictability)
- React: 동일 입력 → 동일 출력
- TanStack Query: 캐시 키 기반 예측 가능한 동작
- Zustand: 순수 함수 기반 상태 변경
5.2 개발자 경험의 일관성
// 모든 라이브러리가 유사한 훅 패턴 사용
const [state, setState] = useState(0); // React
const { data, isLoading } = useQuery({ queryKey, queryFn }); // TanStack Query
const bears = useBearStore((state) => state.bears); // Zustand
// 커스텀 훅으로 추상화 가능
function useShoppingCart() {
// 서버에서 장바구니 데이터 가져오기
const { data: items } = useQuery({
queryKey: ['cart'],
queryFn: fetchCartItems,
});
// 클라이언트 UI 상태
const isOpen = useCartStore((state) => state.isOpen);
const toggleCart = useCartStore((state) => state.toggle);
// 로컬 계산
const total = useMemo(() =>
items?.reduce((sum, item) => sum + item.price, 0) || 0,
[items]
);
return { items, isOpen, toggleCart, total };
}
6. 철학적 진화: React 생태계의 성숙
6.1 단일 책임 원칙의 구현
React 생태계는 “각 도구가 한 가지를 정말 잘하기” 철학으로 진화했다1213:
- React Hooks: 컴포넌트 로직과 생명주기
- TanStack Query: 서버 상태와 비동기 데이터
- Zustand: 클라이언트 전역 상태
- React Router: 라우팅과 내비게이션
6.2 “마법보다는 명시성” 철학
모든 라이브러리가 **“숨겨진 마법보다는 명시적 제어”**를 선호한다1415:
// 명시적이고 예측 가능한 API
const query = useQuery({
queryKey: ['user', id], // 명시적 캐시 키
queryFn: () => fetchUser(id), // 명시적 데이터 소스
staleTime: 1000 * 60 * 5, // 명시적 캐시 정책
enabled: !!id, // 명시적 실행 조건
});
결론
TanStack Query와 Zustand는 React Hooks의 철학을 대체하는 것이 아니라 확장하고 보완한다. 이들은 React의 함수형, 조합 가능, 선언적 철학을 공유하면서도 각각의 전문 영역에서 React의 한계를 해결한다.
핵심 철학적 일치점:
- 조합 우선: 작은 부품들을 조합하여 복잡한 시스템 구축
- 선언적 API: “무엇을” 원하는지 선언하면 “어떻게”는 라이브러리가 처리
- 함수형 패러다임: 순수성, 불변성, 예측 가능성 중시
- 개발자 경험: 최소한의 보일러플레이트, 직관적인 훅 패턴
이러한 철학적 일관성 덕분에 React 개발자는 학습 곡선 없이 자연스럽게 이들 라이브러리를 도입할 수 있으며, 각각의 장점을 조합하여 더 견고하고 유지보수하기 쉬운 애플리케이션을 구축할 수 있다.
Footnotes
-
https://react.dev/learn/reusing-logic-with-custom-hooks ↩ ↩2
-
https://blog.saeloun.com/2024/07/25/functional-programming-in-react/ ↩ ↩2
-
https://blog.logrocket.com/fundamentals-functional-programming-react/ ↩
-
https://ericnormand.me/podcast/is-react-functional-programming ↩
-
https://www.connerbush.com/react-advanced-state-management-context-and-custom-hooks/ ↩
-
https://velog.io/@kjwsx23/Tanstack-Query-리액트-친화적인-비동기-상태-관리-툴에-대한-집중-분석 ↩ ↩2
-
https://namastedev.com/blog/why-you-should-use-zustand-for-react-state-5/ ↩
-
https://www.reddit.com/r/reactjs/comments/1gerrg8/best_way_for_managing_state_globally/ ↩
-
https://techhub.iodigital.com/articles/minimal-state-management-tools ↩
-
https://labs.factorialhr.com/posts/hooks-considered-harmful ↩