카카오스타일(지그재그)이 Jotai 를 도입하기까지 거쳐 온 상태 관리 선택의 역사를 정리한 글이다. Mithril 기반 MVVM, MobX, useReducer + immer 를 차례로 쓰면서 무엇이 불만이었는지를 코드와 함께 밝히고, 왜 Recoil 이 아니라 Jotai 였는지까지 설명한다.
핵심 포인트- 렌더링은 React 가 책임지므로 개발자가 신경 쓸 것은 데이터 모델링과 사용자 행위에 따른 상태 변화라는 것이 글 전체의 전제다.
- 상태에는 Collapse 컴포넌트의 접힘 여부처럼 UI 전용 데이터도 포함되며, 저자는 이것을 MVVM 의 ViewModel 로 본다.
- 2019년 커머스 전환 때는 Redux 대신 MobX 를 골랐다. 전역 상태를 하나의 스토어에 두는 Redux 컨셉에 거부감이 있었다.
- 2021년 표준을 다시 잡으며 useReducer + immer + Ducks 패턴을 시도했지만, 리듀서라는 개념 자체와 동기/비동기 액션 정의가 다른 점이 걸렸다.
- Jotai 는 큰 state 대신 개별 atom 을 정의해 조합한다는 점, 개념이 단순하고 코드가 작다는 점에서 채택됐다.
상세 정리- 선언형 UI 의 의미: jQuery 로 할 일 목록을 만들면 todos 변수(모델)와 `#list` DOM(뷰)을 손으로 동기화해야 한다. React 같은 프레임워크는 그 관계를 선언형으로 정의하게 해 뷰 코드를 단순하게 만든다.
- 같은 흐름이 네이티브에도 와서 SwiftUI, Jetpack Compose 가 나왔고, 자사 앱은 아직 선언형까지는 아니지만 MVVM 으로 뷰와 데이터를 분리하고 있다고 언급한다.
- Mithril 시절: 내부 툴을 Mithril 로 만들며 몇 단계 개선 끝에 MVVM 구성을 잡았다. 당시엔 React 와 큰 차이가 없다고 봤지만 React 는 훅이라는 변화를 겪었고 Mithril 은 정체 끝에 멈췄다. 2019년부터 React 로 정책을 바꿨고 FAQ 같은 일부 프로젝트에 흔적이 남아 있다.
- MobX 선택과 회의: Redux 를 제대로 쓰려면 알아야 할 개념과 부가 라이브러리가 많다는 점, 상태는 영향 범위가 좁을수록 부수 효과가 적다는 관점이 배경이다.
- 훅 시대의 재조사: MobX 는 데코레이터 의존과 훅과의 궁합 문제로 배제했다(최신 버전은 달라졌다고 단서를 단다).
- useReducer 시도: 반복 코드를 줄이려 immer 를 붙이고 파일 구성은 Ducks 패턴을 참고했다. 비동기 처리가 어려워 `use-reducer-async` 를 참고해 비동기 리듀서를 만들고 login 액션을 컴포넌트 밖으로 뺐다.
- 남은 불만 네 가지: 비즈니스 로직 함수를 리듀서라 부르는 것에 대한 거부감, 동기·비동기 액션의 정의 방식이 다른 점, `button_enabled` 같은 계산된 속성과 일반 속성의 사용법 차이, state 와 dispatch 를 여전히 하위 컴포넌트에 넘겨야 하는 점이다.
- Context 검토: Prop Drilling 을 피하려 Context 를 보려 했지만, 한 화면의 상태를 한 Context 에 다 담으면 성능 문제가 있다는 점(그래서 `useContextSelector` 같은 게 나왔다는 점)에서 막혔다.
- Jotai 발견: 프론트엔드 미팅에서 이름이 나와 조사했고, 큰 state 대신 atom 을 정의해 조합한다는 개념이 맞았다. 계산된 속성·반응형 속성 쪽은 아직 해결 못 한 부분이 있다고 솔직히 남긴다.
- Recoil 을 고른 게 아닌 이유: Facebook 이 직접 관리해 장기 유지 가능성은 높다고 봤지만 key 가 존재하고 selector 가 atom 과 구분되는 등 비슷한 개념을 더 복잡하게 정의한다고 판단했다. 초기에 Mithril 을 고른 이유와 같은 기준이다.
- 사용법: 상태는 `atom` 으로 정의하고 `useState` 대신 `useAtom` 을 쓴다. 다른 컴포넌트에서도 같은 atom 을 `useAtom` 으로 읽고 바꾼다.
- 액션은 쓰기 전용 atom 으로 정의하며, 값은 snake_case·함수는 camelCase 라는 사내 컨벤션에 따라 로직 atom 은 camelCase 로 쓴다.
- 스코프: 기본적으로 atom 은 전역 공유이고, 독립 공간이 필요하면 Provider 로 감싼다. Next.js 페이지 단위로 상태를 관리해 페이지 컴포넌트를 Provider 로 감쌌다.
왜 읽나Redux 를 쓸지 말지 고민 중인 프론트엔드 팀에게, 라이브러리 소개가 아니라 실제로 갈아타며 걸렸던 지점들을 순서대로 보여준다.