프론트엔드 상태관리도구에 대해서 알아보자 (1)
💡 상태 관리 방식에 대해서 알아보자
전역 상태 관리의 필요성을 알아보기 위해서 과거 진행했던 데이터 흐름을 먼저 알아보겠습니다.
💡 과거 데이터 흐름
과거 php로 개발하던 시절을 회상하며.. 당시 데이터 흐름은 아래와 같습니다.
- PHP가 HTML을 생성 → 브라우저에 전달
- 사용자가 버튼 클릭 등 이벤트 발생
- jQuery가 DOM을 직접 수정하거나 Ajax로 서버에 요청
- 서버(PHP)가 새로운 HTML을 반환
- jQuery가 해당 HTML을 페이지에 삽입
즉, 서버에서 HTML에 데이터를 삽입하고 렌더링 하는 방식이였고, 클라이언트에서 상태를 관리하는 개념은 없었습니다.
상태 유지를 세션과 쿠키, 폼 제출로 관리(SSR) 방식이라서, 클라이언트에서 렌더링 하기 위해서는 새로고침을 해야 반영이 되었습니다.
(물론, 2010년대 이후 ajax를 통해서 새로고침 없이 데이터가 반영이되도록 가능해졌습니다.)
이후 Laravel 1.0이 출시하면서 PHP에서는 MVC 패턴이 사실상 표준이 되었습니다.
🧱 MVC 패턴의 문제점
- 복잡해지기 쉬움
- 규모가 커지면 컨트롤러가 너무 무거워짐 (Fat Controller 문제)
- 뷰-모델-컨트롤러 간 의존성이 꼬이면 유지보수 헬파이어 시작
- 데이터 흐름이 양방향
- 뷰와 모델이 서로 영향을 주고받음 → 예측 불가능한 상태 변화
- 컴포넌트 기반 UI에 안 맞음
- React, Vue 같은 컴포넌트 기반 프레임워크엔 적합하지 않아서 관리 포인트가 분산 되어 있습니다.
react를 개발한 페이스북(메타)에서도 동일하게 php를 사용하였기 때문에 동일하게 MVC 패턴을 사용하고 있었습니다.
새로운 기능이 추가되거나 규모가 커질수록 복잡한 데이터 흐름을 가지게 되어 예측 불가능한 코드를 만들게 되었고, 상태 변경 여러 버그가 발생하게 되었습니다.
(페이스북의 뉴스피드, 댓글, 알림 같은 UI는 매우 동적이었음)
View가 다양한 상호작용을 위해 여러 개의 Model을 동시에 업데이트하고 Model 역시 여러 개의 View를 업데이트할 수 있는데,
즉, 페이스북은 기존 MVC 패턴으로 동작하는 웹 애플리케이션에서 UI 업데이트가 복잡한 문제를 겪고 있었던 도중 Flux 패턴이 등장하게 되었습니다.
(물론 React가 먼저 등장했고, 그 이후에 flux 패턴이 등장했습니다 😉)
데이터는 단방향으로 흐르며 Action → Dispatcher → Store → View 방향으로 흐릅니다.
동작 원리
- Action(액션): 상태를 변경하는 요청을 보냄
- 어플리케이션에 상태 변경을 트리거 하려면 Action을 통해서 변경합니다.
- 모든 Action은 Dispatcher를 통해서 스토어에 전달됩니다.
- Reducer(리듀서): 액션을 받아 상태를 변경하는 함수
- Store(저장소): 애플리케이션 전역 상태를 저장
- 상태 저장소를 기반으로 상태를 관리하며, 상태가 변경될 때마다 관련된 뷰에 변경 사항된 내용을 전달하며, 스토어는 스스로 불변성을 유지하고 상태 변경에 대한 함수는 순수 함수로 구성되게 구성합니다.
- View(컴포넌트): 상태를 구독하여 UI를 렌더링
- View에서는 상태를 읽고 변경 사항을 감지하여 UI를 변경할 수 있도록 합니다.
React/Vue 이후의 양방향 데이터 바인딩
현대적인 프레임워크(Vue, Angular)에서는 모델(데이터)과 UI가 즉시 동기화되는 양방향 데이터 바인딩을 제공
- 데이터 변경 시 자동으로 UI 업데이트
- UI에서 입력하면 데이터도 즉시 변경됨
즉, 사용자가 입력 필드를 수정하면, 별도 DOM 조작 없이도 데이터가 자동 업데이트 됨.
하지만 Flux 패턴은 하나의 틀일 뿐 라이브러리 별 상태를 관리하는 방식은 다릅니다.
Proxy 패턴 (MobX)
객체의 상태를 Proxy를 이용해 감지하고, 자동으로 변경을 반영하는 방식. 상태 변경이 즉시 적용됨.
동작 원리
- Proxy 객체 생성 → observable()로 상태를 감싸면 자동으로 추적
- 상태 변경 감지 → 값이 변경되면 관련된 컴포넌트가 자동 업데이트
- View(컴포넌트): observer로 감싼 컴포넌트만 필요한 부분을 리렌더링
특징
- 자동 추적 → observable 상태를 변경하면 UI가 자동으로 업데이트됨
- 리액티브 시스템 → Vue.js의 반응형 시스템과 비슷한 개념
- 불필요한 렌더링 최소화 → 필요한 부분만 렌더링됨
✔ 자동 반응형 업데이트 → useState 없이도 상태 변경을 감지하여 UI 반영
✔ 보일러플레이트가 적음 → Redux보다 코드가 간결
✔ 최적화가 쉬움 → observer로 감싼 컴포넌트만 렌더링됨
✖ 명시적인 흐름이 부족함 → 상태가 자동으로 변경되기 때문에 예상치 못한 사이드 이펙트 발생 가능
✖ 디버깅 어려움 → Proxy 내부에서 변경이 이루어지므로 Redux DevTools처럼 상태 변경 추적이 어렵다
Atomic 패턴 (Recoil, Jotai)
상태를 원자 단위로 쪼개어 관리하고, 필요한 컴포넌트만 해당 원자를 구독하도록 한다.
동작 원리
- Atom 생성 → atom()을 사용해 상태를 정의
- Selector 사용 → selector()로 파생된 상태를 계산
- 컴포넌트에서 구독 → useRecoilState()나 useAtom()을 사용하여 상태를 읽고 변경
특징
- 상태를 작은 단위로 관리
- 상태 의존성 추적 가능 → Recoil에서는 selector()를 이용하여 원자 간 관계를 형성
✔ 컴포넌트 단위의 독립적인 상태 관리 → 필요할 때만 특정 상태를 구독
✔ 상태 의존성을 쉽게 구성 가능 → selector()로 계산된 상태를 만들 수 있음
✔ 렌더링 최적화 → 원자 단위로 상태를 관리하므로 성능이 좋음
✖ 복잡한 상태 관리가 어려울 수 있음 → Flux처럼 체계적인 흐름이 없음
✖ 큰 애플리케이션에서는 오히려 관리가 어려울 수 있음
그렇다면 우리에게 가장 어울리는 상태 관리 라이브러리는 뭘까?
🔴 Redux
특징: 가장 많이 사용하는 상태 관리 라이브러리
장점: 명확한 데이터 흐름, 강력한 디버깅 도구, 대규모 프로젝트에 적합
🌀 Recoil
특징: Facebook이 개발한 React에 최적화된 상태 관리 라이브러리
장점: 원자 단위로 상태 분리가 용이하며 비동기 처리가 깔끔함
🐻 Zustand
특징: 경량화되고 직관적인 Hook 기반 라이브러리
장점: 최소한의 설정으로 빠른 개발이 가능하며 소규모 앱에 최적화
🌱 Jotai
특징: Recoil의 컨셉을 더욱 단순화한 라이브러리
장점: 쉬운 학습 곡선과 효율적인 원자 단위 상태 관리
⚡ MobX
특징: 관찰자 패턴 기반의 자동 반응형 상태 관리
장점: 최소한의 코드로 자동화된 상태 관리, 직관적인 개발 경험
그렇다면 우리는 어떻게 상태 관리를 하고 있을까?
우리 프로덕트는 기본적으로 Next + supabase를 사용하고 있기 때문에 상태 관리에 대한 이점은 이미 있습니다.
다음 시간에는 현재 진행중인 프로덕트에 어울리는 상태관리 도구에 대해서 비교 분석 해보겠습니다.
의견 남기기
이 글에 대한 의견이나 질문이 있으시다면 언제든 연락주세요!