본문으로 건너뛰기

안동민 개발노트

본문 시작

상태 관리 라이브러리 소개

Prop Drilling과 공유 상태 문제를 이해하고 Context·Redux Toolkit·Zustand·Jotai의 모델과 적용 규모를 비교합니다.

지난 장에서는 React 핵심 개념(컴포넌트, JSX, state, props)을 학습하며 선언형 UI 기초를 다졌습니다.

특히 useState로 컴포넌트 내부 데이터를 관리하고, props로 부모-자식 간 데이터를 전달하는 방식을 확인했습니다.

하지만 애플리케이션의 규모가 커지고, 여러 컴포넌트가 동시에 동일한 데이터를 공유하거나 업데이트해야 하는 상황이 발생하면 문제가 복잡해지기 시작합니다.

예를 들어, 웹사이트의 상단 내비게이션 바에 표시되는 로그인 사용자 이름과, 프로필 페이지에 표시되는 사용자 이름이 동일한 데이터를 공유해야 할 때를 생각해봅시다.

이러한 상황에서 컴포넌트 간의 데이터 전달이 복잡해지는 문제를 Prop Drilling이라고 합니다.

이는 데이터를 필요로 하지 않는 중간 컴포넌트들을 거쳐 여러 단계의 props를 전달해야 하는 상황을 의미하며, 코드를 읽고 유지보수하기 어렵게 만듭니다.

또한, 여러 컴포넌트에서 동일한 데이터를 독립적으로 관리하다 보면 데이터 불일치가 발생하기 쉽습니다.

이 문제를 해결하고 애플리케이션 상태를 일관되고 예측 가능하게 관리하기 위해 상태 관리 라이브러리(State Management Library)가 등장했습니다.

이번 장에서는 상태 관리 라이브러리의 필요성과 주요 라이브러리의 특징/작동 방식을 간략히 정리합니다.

상태 관리는 공유 데이터를 규칙 있는 변경 흐름으로 묶는다

Prop Drilling이 커지면 중앙 저장소와 구독 구조로 여러 컴포넌트가 같은 값을 기준으로 다시 그려진다.

구간역할대표 패턴확인 질문
UI Event사용자 입력이 변경 요청을 만든다click, input, submit어떤 이벤트가 상태를 바꾸는가
Change Rule정해진 규칙으로 상태를 변경한다action, reducer, setter임의 수정이 숨어 있지 않은가
Store공유 상태의 기준값을 보관한다Redux, Zustand, atom store누가 값을 소유하는가
Subscribers상태를 읽는 컴포넌트가 다시 렌더링된다selector, hook, query cache필요한 부분만 다시 그려지는가

Prop Drilling 문제와 상태 관리의 필요성

React와 같은 컴포넌트 기반 프레임워크에서 데이터는 기본적으로 부모에서 자식으로 props를 통해 전달됩니다.

이 방식은 간단한 애플리케이션에서는 효과적이지만, 다음과 같은 경우에 한계에 부딪힙니다.

Prop Drilling (Prop 지옥)
  • 컴포넌트 트리의 깊이가 깊어질수록, 데이터를 필요로 하지 않는 중간 컴포넌트들까지도 props를 전달해야 하는 상황이 발생합니다.
  • 이는 코드를 지저분하게 만들고, 어떤 데이터가 어디로 전달되는지 추적하기 어렵게 만들며, 리팩토링 시에도 큰 부담이 됩니다.
// 예시: Prop Drilling
<App>
  <Header user={user}> // user 데이터를 Header에 전달
    <Navbar user={user}> // Navbar는 user를 필요로 하지만 Header는 필요 없음
      <Avatar user={user} /> // Avatar는 user를 필요로 함
    </Navbar>
  </Header>
  <Main />
</App>
여러 컴포넌트 간의 상태 공유 및 동기화 어려움
  • 서로 다른 계층에 위치한 컴포넌트들이 동일한 상태를 공유하고 업데이트해야 할 때, propscallback 함수를 사용하는 방식으로는 구현이 복잡해지고 오류가 발생하기 쉽습니다.
  • 예를 들어, 장바구니 담기 버튼이 있는 상품 상세 컴포넌트총 상품 개수를 표시하는 장바구니 아이콘 컴포넌트가 멀리 떨어져 있을 때, 두 컴포넌트가 동일한 장바구니 상태를 참조하고 업데이트해야 합니다.

이러한 문제들을 해결하기 위해, 애플리케이션 전역 또는 특정 도메인의 공유 상태(Shared State)를 중앙에서 관리하는 메커니즘이 필요해졌습니다.

이것이 바로 상태 관리 라이브러리의 핵심 역할입니다.


상태 관리 라이브러리의 기본 개념

상태 관리 라이브러리들은 다양한 구현 방식을 가지고 있지만, 대부분 다음과 같은 공통적인 개념을 공유합니다.

  • 중앙 집중식 저장소 (Centralized Store): 애플리케이션의 모든 공유 상태를 한 곳에 모아둡니다.
  • 상태 변경 규칙: 상태는 정해진 규칙(Action, Mutation, Reducer 등)에 의해서만 변경될 수 있습니다. 이는 상태 변경의 예측 가능성을 높이고 디버깅을 용이하게 합니다.
  • 단방향 데이터 흐름: 사용자 인터페이스에서 발생한 이벤트는 특정 액션을 발생시키고, 이 액션은 상태 저장소의 상태를 변경합니다. 상태가 변경되면, 해당 상태를 구독(subscribe)하고 있는 모든 UI 컴포넌트들이 자동으로 업데이트됩니다.

주요 상태 관리 라이브러리 소개

상태 관리 라이브러리는 앱 규모와 변경 규칙에 맞춰 고른다

작은 앱은 Context나 Zustand가 충분하고, 엄격한 추적과 팀 규칙이 필요하면 Redux가 유리하다.

선택지잘 맞는 상태장점주의점
Context테마, 인증처럼 낮은 빈도 전역값React 기본 기능으로 간단함잦은 업데이트에는 부담
Redux복잡한 비즈니스 상태와 추적 필요규칙과 DevTools가 강함보일러플레이트가 늘 수 있음
Zustand가벼운 클라이언트 store작성량이 적고 빠르게 적용팀 규칙은 별도 합의 필요
Jotai작은 단위 atom 상태상태를 조각내기 좋음의존 그래프 관리 필요
React Query서버 데이터와 캐시로딩, 재검증, 캐시 처리클라이언트 UI 상태와 역할 분리

프론트엔드 생태계에는 다양한 상태 관리 라이브러리들이 존재하며, 각각의 특징과 장단점을 가지고 있습니다.

여기서는 가장 널리 사용되거나 주목받는 라이브러리들을 소개합니다.

Redux

Redux는 React와 가장 오랫동안, 그리고 널리 함께 사용되어 온 상태 관리 라이브러리입니다.

예측 가능한 상태 컨테이너(Predictable State Container)를 지향하며, Flux 아키텍처 패턴을 기반으로 합니다.

  • 핵심 원칙

    단일 진실 원천 (Single source of truth): 모든 애플리케이션 상태는 하나의 거대한 객체 트리로, 단일 저장소(Store)에 저장됩니다.

    상태는 읽기 전용 (State is read-only): 상태를 직접 변경할 수 없으며, 반드시 액션(Action)을 디스패치(Dispatch)해야만 변경할 수 있습니다.

    순수 함수로 변경 (Changes are made with pure functions): 상태를 변경하는 로직은 리듀서(Reducer)라는 순수 함수를 통해 이루어집니다.

    리듀서는 이전 상태와 액션을 받아 새로운 상태를 반환합니다.

  • 장점
    • 예측 가능한 상태 변화: 엄격한 규칙으로 상태 변화를 예측하고 추적하기 용이합니다.
    • 강력한 디버깅 도구: Redux DevTools를 통해 상태 변화를 시간여행(Time-travel Debugging)하며 디버깅할 수 있습니다.
    • 확장성: 미들웨어(Middleware)를 통해 비동기 처리, 로깅 등 다양한 기능을 추가할 수 있습니다 (예: Redux Thunk, Redux Saga).
  • 단점
    • 상대적으로 복잡한 설정: 초기 설정과 보일러플레이트(boilerplate) 코드가 많아 학습 곡선이 높을 수 있습니다.
    • 작은 애플리케이션에는 과할 수 있음: 간단한 애플리케이션에는 오버헤드가 될 수 있습니다.

Redux 개념 흐름 View (React Component) -> Action Dispatch -> Middleware (Optional) -> Reducer -> Store (State Update) -> View (Re-render)

Zustand

Zustand는 간결하고 빠르며 확장성이 좋은 상태 관리 라이브러리입니다.

React Hooks 기반의 가벼운 API를 제공합니다.

  • 특징
    • 간결한 API: Redux나 Context API보다 훨씬 적은 코드로 상태를 정의하고 사용할 수 있습니다.
    • 보일러플레이트 최소화: 상태 정의와 구독이 매우 직관적입니다.
    • 프록시 기반: 내부적으로 Proxy를 사용하여 상태 변경을 효율적으로 감지합니다.
    • React 외부에서도 사용 가능: React 외의 순수 JavaScript 환경에서도 사용할 수 있습니다.
  • 장점
    • 매우 쉽고 빠름: 학습 곡선이 낮고, 즉시 사용하기 좋습니다.
    • 작은 번들 크기: 애플리케이션의 전체 크기를 늘리지 않습니다.
    • 유연성: 다양한 패턴과 함께 사용할 수 있습니다.
  • 단점
    • 명확한 규칙 부재: Redux처럼 엄격한 규칙을 강제하지 않으므로, 팀 내에서 상태 관리 규칙을 명확히 정해야 합니다.

Jotai

Jotai는 Atom을 조합해 공유 상태와 파생 상태를 구성하는 경량 상태 관리 라이브러리입니다.

"상태는 원자적(atomic)이다"라는 철학을 따릅니다.

  • 특징
    • Atom 기반: 상태의 최소 단위를 Atom으로 정의합니다.
    • 간결하고 원자적: 작은 상태 단위를 독립적으로 정의하고 필요한 컴포넌트에서 조합합니다.
    • TypeScript 친화적: 타입 추론이 잘 되도록 설계되었습니다.
  • 장점
    • 매우 가벼움: 작은 번들 크기.
    • 유연성: 다양한 사용 사례에 적용 가능.
    • 뛰어난 성능: React의 렌더링 최적화와 잘 통합됨.

React Context API (+ useReducer)

React 자체적으로 제공하는 Context APIprops를 일일이 전달하지 않고도 컴포넌트 트리를 통해 데이터를 공유할 수 있는 방법을 제공합니다.

useReducer 훅과 함께 사용하면 Redux와 유사한 패턴으로 복잡한 상태 관리가 가능합니다.

  • 장점
    • 별도 라이브러리 설치 불필요: React에 내장되어 있어 추가 의존성이 없습니다.
    • Props Drilling 해결: 중간 컴포넌트가 props를 넘겨받을 필요 없이 필요한 컴포넌트에서 직접 데이터를 구독할 수 있습니다.
  • 단점
    • 전역 상태의 변화: Context의 Provider 값이 변경되면 해당 Context를 구독하는 모든 하위 컴포넌트들이 재렌더링될 수 있어 성능 문제가 발생할 수 있습니다. (최적화 노력 필요)
    • 분리된 스토어 관리 어려움: 여러 Context를 사용할 경우 상태를 분리해서 관리하기 어려울 수 있습니다.
    • Redux DevTools와 같은 강력한 디버깅 도구 부재: 상태 변화 추적이 상대적으로 어렵습니다.

활용: 전역적으로 필요한 테마 설정, 인증 정보 등 자주 변경되지 않거나 컴포넌트 트리를 따라 깊게 전달되는 데이터에 적합합니다.

복잡한 비즈니스 로직이나 빈번한 상태 업데이트가 필요한 경우에는 Redux Toolkit, Zustand, Jotai 같은 전용 라이브러리가 더 적합할 수 있습니다.

상태 관리 도구를 고를 때는 상태의 범위를 먼저 나누면 판단이 쉬워집니다.

React 상태는 범위와 소유자에 따라 저장소를 고른다

모든 상태를 전역 store에 넣기보다 local, shared, server, derived 상태를 분리해야 흐름이 단순해진다.

상태 범위예시좋은 저장 위치피해야 할 것
Local UI모달 열림, 입력 중 값useState, useReducer전역 store에 불필요하게 저장
Shared Client인증 사용자, 장바구니Zustand, Redux, Contextprops drilling으로 깊게 전달
Server CacheAPI에서 온 목록과 상세React Query 같은 캐시 도구로딩·재검증을 직접 전역 상태로 관리
Derived View필터 결과, 합계, 정렬 결과렌더 중 계산 또는 memo원본과 파생값을 둘 다 저장

상태 관리 전략 정리

상태 관리 도구를 고를 때는 라이브러리 이름보다 상태가 머무는 범위와 갱신 빈도를 먼저 나눠야 합니다.

Redux Toolkit, Zustand, Jotai, Context API와 useReducer는 상태 범위와 갱신 규칙에 따라 선택지가 됩니다.

아래 점검표는 상태의 범위와 변경 규칙에 따라 어떤 관리 방식을 고를지 빠르게 판단하도록 돕습니다.

상태 관리 방식 선택 기준

Prop Drilling이 보인다고 모든 값을 전역 store에 넣는 것은 아닙니다. local, shared, server 상태를 구분한 뒤 필요한 도구만 선택합니다.

  1. Local UI
    한 컴포넌트 근처에서 끝나는 값

    입력 중 값, 탭 선택, 모달 열림은 useState로 충분한 경우가 많습니다.

  2. Shared Client
    여러 화면이 동시에 읽는 값

    인증 사용자, 장바구니, 테마처럼 공통 상태면 Context나 store를 검토합니다.

  3. Server Cache
    API에서 오고 재검증이 필요한 값

    목록, 상세, 검색 결과는 캐시와 로딩 상태를 함께 관리합니다.

  4. Derived
    기존 값에서 계산되는 표시 값

    필터 결과, 합계, 정렬 결과는 원본과 따로 저장하지 않는 것이 안전합니다.

  5. Redux가 맞는 경우

    변경 규칙, 기록, 미들웨어, 강한 팀 규칙이 필요한 규모입니다.