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는 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의 장점
•
•
보일러 플레이트 코드가 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 버전도 되지 않은 상황이다.
비교 | 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 |