5. Understanding React State Managemen

서버가 React로 HTML을 먼저 만들고, 브라우저는 그 HTML을 받아서 hydrate로 React 앱으로 되살린다


1. 상태 관리는 왜 필요한가?

1.1 상태 관리는 왜 필요한가?

웹 개발에서 상태란 “애플리케이션의 시나리오에 따라 지속적으로 변경될 수 있는 값”을 의미합니다. UI 상태(다크 모드), URL 상태(쿼리 파라미터), 폼 상태(로딩 중), 서버 데이터 등이 이에 포함됩니다.

Flux 패턴의 등장

초기 웹 애플리케이션(MVC 패턴)은 모델과 뷰가 양방향으로 얽혀 있어 데이터 흐름을 추적하기 어려웠습니다. 페이스북 팀은 이를 해결하기 위해 단방향 데이터 흐름을 가진 Flux 패턴을 제안했습니다.

Action -> Dispatcher -> Store -> View

이후 Elm 아키텍처의 영향을 받아 리듀서(Reducer) 개념을 도입한 Redux(리덕스)가 등장했고, 리액트 상태 관리의 사실상 표준이 되었습니다. 하지만 리덕스는 너무 많은 보일러플레이트(준비 코드)가 필요하다는 단점이 있었습니다.

Context API는 상태 관리 도구가 아니다?

리액트 16.3에서 등장한 Context API는 전역 상태 관리의 구세주처럼 보였지만, 엄밀히 말하면 이는 상태 주입(Injection) 도구이지 상태 관리 도구가 아닙니다.

  • 상위 컴포넌트가 렌더링되면 Context를 구독하는 하위 컴포넌트도 무조건 다시 렌더링되는 성능 이슈가 있기 때문입니다.

  • Context API는 렌더링 최적화 기능이 없으므로, 값이 변할 때 필요한 부분만 리렌더링해주는 별도의 기능이 필요합니다.



2. 상태 관리 라이브러리, 직접 만들어보며 원리 이해하기

리액트 외부에서 상태를 관리하고, 값이 변할 때만 컴포넌트를 다시 그리려면 어떻게 해야 할까요? 책에서는 useState의 한계를 넘어 직접 스토어를 구현하는 과정을 통해 그 원리를 설명합니다.

핵심 원리: 구독(Subscription)

외부 변수(Store)에 상태를 두고, 컴포넌트는 이 스토어의 변경 사항을 구독(Subscribe)해야 합니다.

  1. Store 생성: 컴포넌트 외부(클로저)에 상태 객체를 둡니다.
  2. Subscribe: 컴포넌트가 마운트될 때 “값이 바뀌면 나한테 알려줘(콜백 실행)”라고 스토어에 등록합니다.
  3. Re-render: 값이 바뀌면 스토어는 등록된 콜백들을 실행하고, 컴포넌트는 useStateuseReducer 등을 이용해 강제로 리렌더링을 유발합니다.

이것이 바로 모든 외부 상태 관리 라이브러리(Redux, Recoil, Zustand 등)가 작동하는 기본 메커니즘입니다.



3. 현대 리액트 상태 관리 3대장 분석 (Recoil, Jotai, Zustand)

이제 위 원리가 실제 라이브러리에서 어떻게 적용되었는지 살펴봅시다.

3.1 Recoil: 리액트를 위해 태어난 원자(Atom)

페이스북(Meta) 팀이 만든 라이브러리로, 리액트의 문법과 가장 유사합니다.

구조: <RecoilRoot> 최상위 컴포넌트가 Context를 생성하고, atom 단위로 상태를 관리합니다.

특징:

Atom & Selector: 최소 상태 단위인 atom과 파생 데이터인 selector로 나뉩니다.

Key 필수: 모든 atom은 고유한 문자열 key를 가져야 합니다.

구현: 값이 변경되면 내부적으로 forceUpdate를 호출해 렌더링을 일으킵니다.

아쉬운 점: 아직 정식 버전(1.0.0)이 출시되지 않았고, key 관리가 번거로울 수 있습니다.


3.2 Jotai: Recoil의 영감을 받은 미니멀리즘

Jotai는 Recoil의 Bottom-up(상향식) 모델을 따르되, 더 가볍고 유연하게 개선했습니다.

Key가 없다: 객체의 참조(Reference) 그 자체를 키로 사용(WeakMap 활용)하기 때문에, 개발자가 문자열 키를 관리할 필요가 없습니다.

통합된 API: atom 함수 하나로 일반 상태와 파생 상태(selector)를 모두 만들 수 있습니다.

가벼움: 불필요한 리렌더링을 막고 메모이제이션 최적화를 자동으로 수행합니다.


3.3 Zustand: 작고 빠르고 강력한 중앙 집중형

최근 가장 다운로드 수가 급증하고 있는 라이브러리입니다. Redux처럼 하나의 큰 스토어를 가지지만 사용법은 훨씬 간단합니다.

바닐라 JS 기반: 리액트와 독립적으로 순수 자바스크립트 환경에서도 작동하는 스토어를 만듭니다.

리액트 연동: 리액트 18의 useSyncExternalStore API를 사용하여 동시성 모드를 지원하고 안전하게 상태를 연동합니다.

Selector 최적화: useStore((state) => state.count)처럼 필요한 상태만 쏙 골라낼 수 있어 렌더링 효율이 좋습니다.

장점: 코드가 매우 간결하고 파일 용량이 작습니다(Jotai의 1/3 수준).



4. 결론: 무엇을 선택해야 할까?

리액트 상태 관리의 역사는 “어떻게 하면 전역 상태를 효율적으로 관리하고, 불필요한 렌더링을 방지할 것인가”에 대한 고민의 과정이었습니다.

Redux: 안정적이고 생태계가 크지만, 코드가 복잡합니다.

Recoil/Jotai: 컴포넌트별로 상태를 잘게 쪼개서 관리(Atomic)하고 싶다면 좋습니다. 특히 Jotai는 키 관리가 필요 없어 편리합니다.

Zustand: 하나의 스토어에서 상태를 관리하되, Redux보다 훨씬 가볍고 쉬운 방법을 원한다면 최고의 선택입니다.



reference: 모던리액트 Deep Dive 5장. 리액트 상태관리