Search

React 상태 관리 라이브러리 4가지

주제
Frontend
날짜
2022/11/16

Redux

Redux의 기본 개념 : 세 가지 원칙

1. Single source of truth

동일한 데이터는 항상 같은 곳에서 가지고 온다.
즉, 스토어라는 하나뿐인 데이터 공간이 있다는 의미이다.

2. State is read-only

리액트에서는 setState 메소드를 활용해야만 상태 변경이 가능하다.
리덕스에서도 액션이라는 객체를 통해서만 상태를 변경할 수 있다.

3. Changes are made with pure functions

변경은 순수함수로만 가능하다.
리듀서와 연관되는 개념이다.
Action(액션) – Reducer(리듀서) - Store(스토어)

Store, Action, Reducer의 의미와 특징

Store (스토어)

Store(스토어)는 상태가 관리되는 오직 하나의 공간이다.
컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.
컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.

Action (액션)

Action(액션)은 앱에서 스토어에 운반할 데이터를 말한다. (주문서) Action(액션)은 자바스크립트 객체 형식으로 되어있다.
Simple JavaScript Object
{ type: 'ACTION_CHANGE_USER', // 필수 payload: { // 옵션 name: '하나몬', age: 100 } }
JavaScript
복사

Reducer (리듀서)

Action(액션)을 Store(스토어)에 바로 전달하는 것이 아니다.
Action(액션)을 Reducer(리듀서)에 전달해야한다.
Reducer(리듀서)가 주문을 보고 Store(스토어)의 상태를 업데이트하는 것이다.
Action(액션)을 Reducer(리듀서)에 전달하기 위해서는 dispatch() 메소드를 사용해야한다.

흐름도

Action(액션) 객체가 dispatch() 메소드에 전달된다.
dispatch(액션)를 통해 Reducer를 호출한다.
Reducer는 새로운 Store 를 생성한다.

Redux의 상태 관리 도식화

왜 이런 공식을 따를까?

데이터가 한 방향으로만 흘러야하기 때문이다.

Redux에서 위 개념을 구현하는 두 가지 방법

1. mapStateToProps()

2. Redux hooks (비교적 최근에 나온 기술)

useSelector
useDispatch

Redux의 장점

상태를 예측 가능하게 만든다. (순수함수를 사용하기 때문)
유지보수가 편하다. (복잡한 상태 관리와 비교)
디버깅에 유리하다. (action과 state log 기록 시) → redux dev tool (크롬 확장)
테스트를 붙이기 용의하다. (순수함수를 사용하기 때문)

Redux의 단점

Redux는 강력한 기능을 제공하지만, 기본적인 store 구성을 위해 많은 보일러 플레이트와 장황한 코드를 작성해야 한다.
비동기 데이터 처리 또는 계산된 값 캐시와 같은 중요한 기능은 라이브러리의 기능이 아니며, 이를 해결하기 위해 또 다른 라이브러리를 사용해야 한다.
만약 selector가 동적인 prop을 받는 경우 이값을 정확하게 memoization하는 것은 어려운 일이다.
API 통신을 react-query로 변경하고 프로젝트를 진행하는 요즘에는 Redux를 통한 전역 상태 관리를 하는 경우가 무척 줄거나 아예 사용하지 않는 프로젝트들이 생겨났다.

Recoil

Redux나 Mobx가 잘못된 것일까?

기존의 상태 관리 라이브러리들은 어떠한 문제도 없다.
중요한 점은 상태 관리 라이브러리들이 React 라이브러리가 아니라는 점이다.
store는 "외부요인으로" 취급되는 것이기 때문에 React의 내부 스케줄러에 접근할 수 없다.
지금까지는 이것이 중요하지 않을 수도 있었다. 그러나 동시성 모드가 등장하며 이야기가 달라졌다.
React와 동시성 모드를 손쉽게 사용할 수 있는 해결 방안.
Recoil은 내부적으로 React의 상태를 사용하고 있으며, 동시성 모드에 대한 지원도 곧 추가될 것이다.

Context API를 사용하는 것은 어떨까?

개인적으로 새로운 Context는 locale/theme와 같은 낮은 빈도의 업데이트에 사용 가능하다고 생각한다. 또한 이전에 Context를 사용했던 방법으로 사용해도 좋다. (즉 정적인 값, 구독을 통해 업데이트를 전파) 하지만 Context는 Flux와 같은 상태 관리 시스템을 대체할 수 없다.
페이스북 엔지니어 Sebastian Markbage
React가 가진 상태 공유 솔루션인 Context API도 한계가 있다.
반복적이고 복잡한 업데이트에 사용할 경우 비효율적이다.
React-Redux 팀도 이전 버전과 비교하였을 때 상당한 성능 문제가 있어 6버전에서 Context API로 재작성한 라이브러리의 일부를 롤백해야 했다.
현재 React-Redux는 store 참조를 전달할 때만 컨텍스트를 사용한다.

Context API 문제점

Context API는 데이터 서브셋을 대상으로 변경을 감지하고 업데이트할 수 없다.
Provider 하위의 모든 consumer들은 Provider 속성이 변경될 때마다 다시 렌더링된다. (https://reactjs.org/docs/context.html#before-you-use-context)
만약 Provider의 값이 배열이나 객체인 경우 구조가 조금이라도 변경된다면, 그 Context를 구독하고 있는 하위의 모든 것 (컴포넌트가 그 값의 일부분만 사용하더라도)이 다시 렌더링될 것이다.
memoization을 통해 일부 문제를 해결할 수 있지만, 모든 것을 해결할 수 있는 방법은 아니며 한계가 있다
각각의 이미지가 각각의 Context를 가지고 있다고 가정해보자.
정확한 이미지 수를 알고 있다면 아무 문제가 없다. 하지만 이미지를 동적으로 변경할 수 있고, 추가할 수 있다면? 새로운 이미지에 Context Provider를 추가하여 컴포넌트 트리를 다시 구성하고 전체 서브 트리를 다시 마운트해야한다.
더 좋지 않은 방법이다. 아래 GIF를 통해 이 문제를 보자.
(동적으로 provider를 추가하면 전체 하위 트리가 다시 마운트됨)
3번째 슬라이드에서 Context Provider를 추가한 후 하위의 모든 것이 다시 마운트된다.
성능에도 좋지 않을 뿐 아니라 Provider와 컴포넌트 트리 노드 사이에 강한 커플링이 생긴다.

Recoil의 장점

Recoil은 배우기 쉽다. API가 단순하고 이미 hook을 사용하고 있는 사람들에게 익숙할 것이다.
Recoil을 시작하기 위해서는 어플리케이션을 RecoilRoot로 감싸고, 데이터를 atom이라는 단위로 선언하여 useState를 Recoil의 useRecoilState로 대체해야 한다.
새로운 구문을 배우거나 수많은 보일러 플레이트 코드를 설정할 필요가 없다. (selector get/set 구문이 약간 이상하지만, 정말 간단한 개념이다).
컴포넌트가 사용하는 데이터 조각만 사용할 수 있고, 계산된 selector를 선언할 수 있으며, 비동기 데이터 흐름을 위한 내장 솔루션까지 제공한다.
또한 비동기 데이터, 상태 지속성, 매개변수화된 selector를 처리할 수 있는 솔루션을 제공하기 때문에 많은 고통이 사라진다.
동적 키로 atom을 만들고, selector에 인자를 보내는 등 모두 간단하게 할 수 있다.
그리고 앞에서 말한 바와 같이 곧 React 동시성 모드에 대한 지원도 될 것이다.

Recoil 요약

심플 & Atom
매우 매우 심플하게 글로벌한 데이터를 관리할 수 있어서 아주 좋다는 생각을 했다. 사실 Atomic패턴으로 상태관리를 하는 건 Recoil을 통해서 처음 접하는데, 굉장히 매력적이다. (근데, 밑에서 리뷰할 Jotai는 atom에서 key값 마저 빼버려서... 난 Jotai를 쓸 거다 ㅎㅎㅎ)
클라이언트 상태관리 추천
실무에서는 CRUD를 조합한 다양한 비동기 처리가 필요한데, 이 복잡한 것을 Recoil로 처리하기에는 무리가 있을 것이라고 판단이 된다.즉, 클라이언트 사이드에서의 상태관리로는 아주 좋은 선택지가 될 것 같다.
Powered by Facebook
급변하는 신기술의 바다에서 페이스북팀의 공식 서포트는 무시할 수 없는 매력적인 요소이다.

zustand

보일러 플레이트가 최소화된 상태관리 solution. Store 형태임에도 굉장히 간단하게 상태관리 구성이 가능하다.
zustand는 redux에서 영감을 받아 제작했다.

주요 특징

간단한 사용법

상태 구독(subscribe) 가능

리덕스와 유사하다

action 기반의 상태관리이며 객체 형태의 Store를 가진다.
state 의 객체 property의 변화는 감지를 못하기 때문에 immer를 사용하는 것이 좋다.

리액트 없이도 사용이 가능

atomic 한 형식이 아니라 외부 store를 이용하는 방식이라 그런지 리액트 없이 바닐라 자바스크립트로도 사용 가능하다.

zustand의 장점

store 구현 방식 및 변경 방식이 간단해 보일러플레이트 코드가 매우 줄어든다.
익히기가 굉장히 쉽다. 아래 주요 특징의 예시를 보면 기본적인 사용법은 사실상 모두 익힌것이다. (그만큼 공식문서도굉장히 짧다 !)
Provider로 감쌀 필요가 없다.
context 방식보다 리렌더링이 줄어든다.

zustand vs Redux-Toolkit (RTK)

RTK의 11.8mb라는 큰 사이즈와 달리 zustand는 187kb라는 압도적으로 작은 크기를 가지고 있다.
작은 크기에 걸맞게 RTK와 달리 무척 간단하게 설정하여 사용할 수 있다.
ex. RTK provider vs zustand not provider
zustand의 subscribe기능을 사용하면 자주 렌더링 되는 항목에 대해서 랜더링을 유발하지 않고 변경하는 처리하는 어찌 보면 소소하지만 강력한 기능도 존재한다.
thunkAPI 등의 RTK 기능들을 사용하지 않는다면 충분히 zustand로 갈아탈만한 조건이 된다고 생각한다.
비교
zustand
redux-toolkit (RTK)
결과
번들사이즈
187kb
11.8mb
zustand
Provider 여부
X
O
zustand
설정 복잡도
간단
상대적으로 복잡
zustand
Transient updates
O
X
zustand
다운로드 수
646k
1798k
rtk
깃허브 Star
20.6k
8.3k
zustand

Jotai

Jotai는 Context의 re-rendering 문제를 해결하기 위해 만들어진 React 특화 상태관리 라이브러리.
Recoil 에서 영감을 받아, atomic 한 상태관리 방식으로 구성된다. (bottom-up 방식)

주요 특징

atom

atom 단위로 상태를 관리하며 atom 을 이용해서 state를 생성한다.
useState 와 같은 인터페이스인 useAtom 을 이용하여 사용한다.
useAtom 외에도 atom을 사용할 다른 유틸 훅들을 제공한다.
실제 값을 들고 있는 것은 아니고 일종의 atom ‘정의’ 이다.
인자값에 initialValue를 넣어주면 된다.
Recoil 과 다르게 atom 에 키값을 넣어줄 필요가 없어서 코드길이가 줄어든다.

Provider와 Scope

Jotai는 기본적으로 Provider를 사용하지 않아도 된다. Provider가 없더라도 atom을 선언할 때 설정된 기본 값을 가진 atom 을 전역적으로 사용할 수 있다.
이는 Jotai가 내부적으로 react 의 context API를 이용하기 때문에 가능한 것인데, context API 도 사실 Provider 없이 사용 가능하다.
다만, Provier가 없다면 하위 컴퍼넌트들에서 구독하는 context들이 리렌더링이 되지 않을 것이다.
Jotai 는 이것을 가능하게 만들었으며, Provider 하위 컴퍼넌트 전체가 리렌더링되는 (혹은 메모이제이션을 추가적으로 해주어야하는) 불편함을 개선했다고 보면 된다.

리렌더링 최소화

대용량 오브젝트나 array 형태의 state에 대해 변경사항이 생겼을 때, 일반적으로 해당 state를 사용하는 모든 컴퍼넌트에 대해서 리렌더링이 발생하는데 그런 문제점을 optics라는 외부 라이브러리를 integration 해서 해결했다.
https://jotai.org/docs/advanced-recipes/large-objects

Jotai의 장점

기본적으로 re-rendering 문제를 줄여주고, selectAtomsplitAtom 과 같은 re-rendering 을 줄이기 위한 유틸들도 지원한다.
보일러 플레이트 코드가 redux에 비하면 현저하게 줄어든다.
앞으로 React 의 주요 feature일 Suspense(Concurrent mode)를 적용하는데에 적합하게 설계되었다.
Jotai 가 강조하는 두가지 특징
Primitive
리액트 기본 state 함수인 useState 와 유사한 인터페이스
Flexible
atom들끼리 서로 결합 및 상태에 관여할 수 있고, 다른 라이브러리들과 원할한 결합을 지원한다.
다른 상태 관리 라이브러리들과의 Integration을 제공

Jotai vs Recoil

Jotai(830kb) 역시 Recoil(2.17mb) 대비하여 압도적인 크기 차이가 존재한다.
거기에 RTK, zustand, Jotai 모두 TS로 작성된 것에 비해 Recoil은 JS로 작성되어 있다는 단점이 존재한다.
그리고 소소하지만 atom에 key값을 넣어줄 필요가 없어 코드의 길이가 줄어드는 효과도 존재한다.
Jotai와 Recoil의 비교는 zustand와 RTK보다 더 간단한데, 활발하게 업데이트가 진행되고 있는 Jotai에 비해 Recoil은 아직 1 버전도 되지 않은 상황이다.
이건 여담이지만 Kent.C.Dodds가 recoil보다 jotai를 더 선호한다고도 이야기했다.
비교
Jotai
Recoil
결과
번들 사이즈
830kb
2.17mb
Jotai
Key 사용 여부
X
O
Jotai
TS
O
X
Jotai
다운로드 수
130k
303k
Recoil
깃허브 Star
9.6k
17.4k
Recoil

zustand vs Jotai

zustand, Jotai 모두 일본의 한 개발자가 만든 상태 관리 라이브러리이다.
두 라이브러리 모두 훌륭한 라이브러리들이고 여기부터는 취향에 가까운 영역이라고 생각한다.
Jotai의 문서에서는 아래와 같은 경우로 사용법을 나누고 있다.
useState + useContext 대체하는 경우 - Jotai
react 이외에서도 사용해야 되는 경우 - zustand
code splitting이 중요한 경우 - Jotai
redux devtools를 선호하는 경우 - zustand
react suspense를 활용하고 싶은 경우 - Jotai
이외에도 bottom-top(Jotai)와 top-bottom(zustand)로 이루어져 있으면 Jotai의 경우 react-query 등의 다른 라이브러리들과 쉽게 연동할 수 있다는 장점이 존재한다.
비교
zustand
Jotai
결과
번들 사이즈
187kb
830kb
zustand
devtools
O
O
비슷함
연동성
O
Jotai
설정 복잡도
간단
간단
비슷함
suspense 지원
X
O
Jotai
state 모델
단일 스토어 형태
primitive atoms 형태
비슷함
형태
useStore 로 거의 단일화되어있음
useState 의 인터페이스를 기반으로한 여러 util (예 : useAtom)
비슷함
방식
Top-down
Bottom-up
비슷함
Provider
필요 없음
일반적으로는 필요함
zustand
바닐라 자바스크립트
O
X (React 만 지원)
zustand
깃허브 Star
20.6k
9.6k
zustand
다운로드 수
646k
130k
zustand

npm trends

jotai vs react-redux vs recoil vs zustand