Redux, Recoil은 React 상태관리 라이브러리이다.
- Redux: Flux 아키텍쳐(패턴) 기반
- Recoil: Atomic 모델 기반
장단점
Recoil
😊장점: React의 useState
훅과 비슷하게 동작하며 직관적이면서 간단한 구조. 코드의 양이 적다.
🥲단점: Redux처럼 안정적인 Devtool이 아직 없다. snapshot
이라는 개념이 존재하지만, 직관적으로 볼 수 있는 것은 아니고 콘솔을 이용하는 형태로 볼 수 있다.
✅ 디버깅의 측면에서 봤을 때 리덕스가 더 유리하다.
Recoilize라는 Devtool이 있는데 작은 버그들이 있다고 한다. 실제로 recoil을 이용해 개발하시는 분들은 console을 더 선호하신다고 한다.
Redux
😊장점: 상태값의 변경 사항을 Redux Devtools를 이용해 직관적으로 볼 수 있는 방법을 제공한다. 이로 인해 전역으로 관리해야 하는 상태값이 많아질 경우 디버깅이 상대적으로 Recoil에 비해 더 편해질 수 있다.
🥲단점: 작은 상태 하나를 변경하려고 해도 actions
, reducer
, type
등 보일러 플레이트 코드를 많이 작성해야 하는 번거로움이 있다.
✅ Recoil에 비해 상대적으로 코드를 작성하는 양이 많다.
비동기 처리 관련(부가 기능 관련)
Recoil
Selector
를 사용한다. Selector
는 Recoil 에서 비동기 처리를 위해 사용하며, 기본적으로 값을 캐싱한다.
들어왔던 적이 있는 값을 기억하고 있기 때문에, 같은 응답을 보내는 api call에 대해서는 추가적으로 요청하지 않아 성능적으로 유리하다.
또한 이는 내장되어 있는 기능이기 때문에 redux 처럼 따로 미들웨어를 설치해줄 필요는 없다.
Redux
비동기 처리에 관련한 부분은 redux-thunk
혹은 redux-saga
, redux-toolkit
등을 이용한다.
(비동기 처리 뿐만 아니라 다른 여러가지 기능을 포함해서)
하지만 예를 들어 redux-saga
를 사용하게 된다면 제너레이터와 이펙트(redux-saga) 등에 대한 이해가 필요하게 된다. (러닝 커브 상승)
또한 결국 이를 사용하기 위해서는 미들웨어를 추가적으로 설치해줘야 한다.
대규모 상태를 관리해야하는 프로젝트에는 Redux를 사용하는 것이 좀 더 적합할 것으로 생각된다.
또한 이런 대규모 상태를 감시, 디버깅하기 위한 안정적인 devtool을 가지고 있기 때문에 안정성 면에서는 redux가 더 나은 것 같다.
그러나 상대적으로 적은 코드를 작성하는 경우에는 recoil이 redux에 비해 유리하다고 생각한다.
Redux
- Flux 아키텍쳐의 데이터 흐름은 단방향이다.
- Action : Action은 데이터의 상태를 변경하는 명령어이다. Action을 생성하여(type, payload) 이를 Dispatcher 에게 전달한다.
- Dispatch : 모든 Action을 전달받아 Store 에게 전달해준다.
- Store : State가 저장되어 있는 공간이다. Dispatcher를 통해서 가져온 Action을 확인해 내부에 저장된 State를 변경해준다.
- Store에서 State 변경 시 change 이벤트가 발생하면서 View에게 State가 변경되었다는 걸 알려준다
- View : React를 통해 만드는 코드들이다. Store에 저장된 데이터를 가져와서 View에 뿌려주고, View는 해당 데이터들을 가지고 와서 화면에 렌더링한다. Store로 부터 State가 변경됨을 전달받으면 View에서 사용하는 State는 업데이트가 일어난다. (Rerender)
보고 체계를 예로 들면, 사령부에서 지시(Action)가 내려오고, 집배원(Dispatcher)이 해당 지시를 가지고 내려온다. 해당 지시를 통해서 병사는 창고(Store)에 저장된 물건(데이터 혹은 state)을 변경하여 꺼내서 훈련할 때 사용한다. (View에 렌더링 하는 과정을 비유)
// action types
export const UPDATE_NAME = `UPDATE/NAME`;
// action creator function
export const updateNameAction = name => ({type: UPDATE_NAME, payload: {name}});
// state
const initialState = {
name: '',
};
// reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_NAME :
return {
...state,
name: action.payload.name,
}
default :
return state
}
};
export default reducer
// store.js
import {combineReducers, createStore} from 'redux';
import testReducer from './modules/test/index';
const rootReducer = combineReducers({
testReducer
});
const store = createStore(rootReducer);
export default store
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
// redux
import store from './store/index';
import {Provider} from 'react-redux'
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
// component
import React, {useEffect} from "react";
import {useSelector, useDispatch} from "react-redux";
import {
updateNameAction,
} from "../../store/modules/test";
export default function TestComponent() {
const dispatch = useDispatch();
const nameState = useSelector(state => state.testReducer.name);
const dispatchUpdateName = (name = 'basic') => {
dispatch(updateNameAction(name)); // action
};
useEffect(() => {
dispatchUpdateName();
}, []);
return (
<div>
<p>{nameState}</p>
</div>
)
}
Recoil
- Atom 이라는 상태 단위로 상태 관리
- 간단히 Atom을 비눗방울로 추상화 할 수 있다. 우리가 만드는 Web Application 을 구조화 한다면 그 구조의 상단에 atom 이 비눗방울처럼 둥둥 떠다니는 것이다. 개발을 하다가 어떤 상태가 필요하다면 그 상태(비눗방울)만 쏙 빼서 쉽게 사용할 수 있다.
- 저장소 개념보다는 작은 상태 단위(Atom)로 관리한다.
- 상태 변경으로 인한 불필요한 렌더링이 발생하지 않는다.
- 상태를 구독한 컴포넌트만 리렌더링이 발생한다.
- selector를 통해 캐싱 (기본적으로 값을 캐싱함)
*Atom: 데이터 조각/상태 조각
*Selector: 아톰에서 파생된 데이터 조각 / 데이터를 반환하는 순수 함수
RecoilRoot
Recoil 을 시작하기 위해서는 index.tsx(또는 index.jsx)에서 렌더링하고 있는 root 를 RecoilRoot 를 통해서 감싸줘야 한다.
마치 Redux에서 <Provider>를 통해서 App 에 Store 연결해주는 것과 비슷한 과정이다.
Redux 에서는 하나의 Store를 연결해주는 과정이지만, Recoil 에서는 Atom이 떠다니는 Root 를 설정해준다고 추상화하면 좋을 것 같다.
import * as ReactDOMClient from "react-dom/client";
import { RecoilRoot } from "recoil";
import App from "./App";
const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<RecoilRoot>
<App />
</RecoilRoot>
)
;
Atom 만들기
Atom 에는 우리가 사용할 상태(state)를 담는다. 쉽게 말하면, 우리가 전역적으로 사용하길 원하는 state 를 atom 이라는 비눗방울로 띄워서 어디서든지 사용할 수 있도록 만드는 것이다.
import { atom } from "recoil"
export const user = atom({
key: "user",
default: {
id: "Admin",
pwd: "Admin",
},
});
export const counting = atom({
key: "counting",
default: 0,
});
여기서 key 는 atom 을 구분해줄 고유값이며, default 는 해당 key 값을 가진 atom 의 기본값으로 설정해줄 value 를 넣어주면 된다. (redux의 initialState 같은?)
이렇게 간단한 코드로 하나의 전역 상태를 만들 수 있다.
Atom 사용하기
import { useRecoilState } from "recoil";
import { counting } from "./store";
export function Example() {
const [count, setCount] = useRecoilState(counting);
const handleIncrease = () => {
setCount((prev) => prev + 1);
}
return (
<div>
<span>{count}</span>
<button onClick={handleIncrease}>increase</button>
</div>
);
}
atom 을 사용하는 방법은 useState 를 통해서 state 를 사용하는 방법과 비슷하다.
useRecoilState 라는 hook 을 recoil 라이브러리에서 가져와, 위에 정의한 atom 의 이름을 넣어주면서 값을 추적해서 값을 변경할 수 있다.
selector
selector 는 atom 을 활용해 개발자가 원하는 대로 값을 뽑아서 사용할 수 있는 API 이다.
function selector<T>({
key: string,
get: ({
get: GetRecoilValue,
getCallback: GetCallback,
}) => T | Promise<T> | RecoilValue<T>, // 타입 T에 해당하는 값, T를 리턴하는 Promise,
set?: (
{
get: GetRecoilValue,
set: SetRecoilState,
reset: ResetRecoilState,
},
newValue: T | DefaultValue, // setter로 전달하는 값은 T 타입 값이어야 한다.
) => void,
}
)
출처
- [velog] Redux vs Recoil - 어떤 걸 골라야할까?