today's helen

seize!

web

상황별 최적의 상태 관리 선택 가이드 (Context API, Zustand, Jotai)

yooncandooit 2026. 7. 8. 15:54
반응형

프로젝트를 시작할 때마다 컨벤션을 정할 때 나오는 질문이 있죠 !

"저희 프로젝트 전역 상태 관리 뭐 쓸까요?"

저는 라이브러리를 써야하면, "zustand요!"라고 답하곤 하는데 사실 다른 건 잘 몰라서 그냥 선택하는 경우가 많았어요. 명확한 기준 없이 선택하고 있는 건 아닐까 싶어,
이번 글에서는 전역 상태 관리 라이브러리를 선택하는 명확한 기준을 함께 고민해보려고 합니다.

그래서

  1. 3가지 라이브러리를 같은 예시로 직접 비교해보고
  2. 실제 리렌더링 차이를 로그로 확인하고
  3. 프로젝트 상황에 맞는 선택 기준 정리하려합니다!

무엇보다 전역 상태가 정말 필요한지부터 먼저 고민해봐요!


Q. 전역 상태 관리가 정말 필요한가요?

전역 상태 관리 라이브러리를 도입하기 전에, 정말 필요한지부터 생각해봐야 합니다.

전역으로 관리하면 좋은 것들:

  • 여러 페이지에서 공유하는 장바구니
  • 사용자 인증 정보
  • 테마, 다크모드

굳이 전역으로 관리하지 않아도 되는 것들:

  • useState + props로 해결 가능한 경우
  • children 패턴으로 해결 가능한 경우
  • 상태를 최소 공통 부모로 끌어올릴 수 있는 경우

테마나 인증 정보 외에 전역으로 관리할 게 뭐가 있을까요? 이 질문에 명확한 답이 없다면, 전역 상태 관리가 필요하지 않을 수도 있습니다.

아래 아티클도 참고해보세요!

전역 상태 관리를 사용하지 않는 7가지 이유


예시로 직접 비교하기

간단한 카운터 예제로 세 가지 라이브러리의 리렌더링 차이를 확인해보겠습니다.

![]

  • Count: 0 → count 값 표시 (CountDisplay 컴포넌트)
  • Increment 버튼 → 증가 버튼 (IncrementButton 컴포넌트)

1. Context API

const { count } = useCount();
const { plusOne } = useCount();

특징: 객체 전체를 구독
결과: count만 바뀌어도 모든 컴포넌트가 리렌더링됨

Context API는 객체 전체를 구독하기 때문에, count 값만 변경되어도 increment 함수만 사용하는 버튼 컴포넌트까지 리렌더링됩니다.

2. Zustand

(state) => state.count
const handleClick = useStore(
  (state) => state.increment
);

특징: count, increment를 각각 구독
결과: count가 변해도 handleClick만 사용하는 버튼 컴포넌트는 리렌더링되지 않음

Zustand는 필요한 상태만 선택적으로 구독할 수 있어서, 해당 상태가 변경될 때만 컴포넌트가 리렌더링됩니다.

3. Jotai

const count = useAtomValue(countAtom);
const setCount = useSetAtom(countAtom);

특징: atom 단위로 더 세분화해서 관리

Hook 종류별 차이:

  • useAtomValue - atom 값 읽기 (값 바뀌면 리렌더링 ⭕)
  • useSetAtom - 값 쓰기만 (값 바뀌어도 리렌더링 ❌)
  • useAtom - 읽기 & 쓰기 (값 바뀌면 리렌더링 ⭕)

결과:

  • CountDisplay는 countAtom을 구독
  • CountButton은 incrementAtom을 구독
  • useSetAtom은 값을 쓰기만 하므로 리렌더링 안 됨
  • 서로 다른 atom을 구독하므로 서로 영향을 받지 않음

+1 버튼 클릭 (setCount 사용) → countAtom만 변경됨 → CountDisplay만 리렌더링! → CountButton은 리렌더링 안 됨

정리: 프로젝트 상황에 맞는 선택 기준

비교

  • Context API: 객체 전체를 구독해서 모든 컴포넌트가 리렌더링됨
  • Zustand: 필요한 상태만 구독해서 해당 컴포넌트만 리렌더링됨
  • Jotai: atom 단위로 세분화해서 useSetAtom을 쓰면 아예 리렌더링이 안 됨

선택 기준 (정답은 없지만..)

먼저, 전역 상태가 필요한지 고민하고:

  1. 소규모 프로젝트나 간단한 전역 설정이라면 → Context API
  2. 중규모 프로젝트에서 빠르게 도입하고 싶다면 → Zustand
  3. 복잡한 상태 의존성이 있다면 → Jotai

+ Zustand를 제대로 사용하고 있나요?

Todo 리스트를 만들면서 Zustand를 사용했었는데, 문득 제가 Zustand를 잘 쓰고 있는지 궁금하더라구요
그래서 다시 코드를 들여다보니 분명 특정 값만 구독하기에 body만 렌더링 되야하는데 header와 body 두 개가 렌더링 되는 문제가 있었어요

그래서 코드를 다시 돌아보는데 문제는 역시 제 코드에 ...

잘못된 사용 예시

const { todos, completedCount, toggleTodo } = useTodoStore();

이렇게 구조 분해 할당으로 사용하면, 내부적으로 모든 값에 대해 구독하기 때문에 Zustand의 강점을 살리지 못합니다! 🙅‍♀️🙅‍♀️

올바른 사용 방법

각각 따로 구독하거나

const todos = useStore((state) => state.todos);
const completedCount = useStore((state) => state.completedCount);
const toggleTodo = useStore((state) => state.toggleTodo);

또는 useShallow 사용:

import { useShallow } from 'zustand/react/shallow';

const { todos, completedCount, toggleTodo } = useTodoStore(
  useShallow((state) => ({
    todos: state.todos,
    completedCount: state.completedCount,
    toggleTodo: state.toggleTodo,
  }))
);

useShallow를 사용하면 선택한 값들이 실제로 변경되었을 때만 리렌더링이 발생합니다!

body만 잘 리렌더링되는 모습

마무리

전역 상태 관리 라이브러리를 선택할 때는 단순히 "인기가 많아서" 또는 "익숙해서"가 아니라, 프로젝트의 특성과 요구사항을 고려해서 명확한 기준을 갖고 선택하도록 해요!

무엇보다 전역 상태가 정말 필요한지부터 고민하고, 필요하다면 각 라이브러리의 특성을 이해하고 상황에 맞게 선택합시다 !!

반응형