Redux vs. Recoil

송은·2023년 6월 13일
0

Redux, Recoil은 React 상태관리 라이브러리이다.

  • Redux: Flux 아키텍쳐(패턴) 기반
  • Recoil: Atomic 모델 기반

비교

장/단점

⚛️ Redux

  • 장점: 상태값의 변경 사항을 Redux Devtools를 이용해 직관적으로 볼 수 있는 방법을 제공한다. 이로 인해 전역으로 관리해야 하는 상태값이 많아질 경우 디버깅이 상대적으로 Recoil에 비해 더 편해질 수 있다.

  • 단점: 작은 상태 하나를 변경하려고 해도 actions, reducer, type 등 보일러 플레이트 코드를 많이 작성해야 하는 번거로움이 있다.

👉 Recoil에 비해 상대적으로 코드를 작성하는 양이 많다.


🔀 Recoil

  • 장점: React의 useState 훅과 비슷하게 동작하며 직관적이면서 간단한 구조. 코드의 양이 적다.

  • 단점: Redux처럼 안정적인 Devtool이 아직 없다. snapshot이라는 개념이 존재하지만, 직관적으로 볼 수 있는 것은 아니고 콘솔을 이용하는 형태로 볼 수 있다.

👉 디버깅의 측면에서 봤을 때 리덕스가 더 유리하다.


비동기 처리 관련(부가 기능 관련)

⚛️ Redux

Redux는 비동기 처리 뿐만 아니라 다른 여러가지 기능을 포함하여 redux-thunk 혹은 redux-saga, redux-toolkit 등을 이용한다.

하지만 예를 들어 redux-saga를 사용하게 된다면 제너레이터와 이펙트(redux-saga) 등에 대한 이해가 필요하게 된다. (러닝 커브↑)
또한 결국 이를 사용하기 위해서는 미들웨어를 추가적으로 설치해줘야 한다.

대규모 상태를 관리해야하는 프로젝트에서 대규모의 상태 감시, 디버깅하기 위한 안정적인 devtool을 가지고 있기 때문에 안정성 면에서는 Redux가 더 나은 것 같다.

그러나 상대적으로 적은 코드를 작성하는 경우에는 Recoil이 Redux에 비해 유리하다고 생각한다.


🔀 Recoil

Selector를 사용한다. Selector는 Recoil 에서 비동기 처리를 위해 사용하며, 기본적으로 값을 캐싱한다.

들어왔던 적이 있는 값을 기억하고 있기 때문에, 같은 응답을 보내는 API 호출에 대해서는 추가적으로 요청하지 않아 성능적으로 유리하다.
또한 이는 내장되어 있는 기능이기 때문에 Redux처럼 따로 미들웨어를 설치해줄 필요는 없다.


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에 렌더링 하는 과정을 비유)


사용법

1. 액션 생성 함수, Reducer 작성

// 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

2. Store 생성

// store.js

import {combineReducers, createStore} from 'redux';
import testReducer from  './modules/test/index';

const rootReducer = combineReducers({
    testReducer
});

const store = createStore(rootReducer);

export default store

3. Store 연결

import App from './App';

// 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')
);

4. 리덕스 적용

// 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); // 스토어에서 state 가져오기
  
    const dispatchUpdateName = (name = 'basic') => {
        dispatch(updateNameAction(name)); // action
    };
   

    useEffect(() => {
        dispatchUpdateName();
    }, []);

    return (
       <div>
        <p>{nameState}</p>
       </div>
    )
}

Recoil

Recoil 모델 구조

  • Atom이라는 상태 단위로 상태 관리
    간단히 Atom을 비눗방울로 추상화 할 수 있다. 우리가 만드는 Web Application을 구조화 한다면 그 구조의 상단에 Atom이 비눗방울처럼 둥둥 떠다니는 것이다. 개발을 하다가 어떤 상태가 필요하다면 그 상태(비눗방울)만 쏙 빼서 쉽게 사용할 수 있다.
  • 저장소 개념보다는 작은 상태 단위(Atom)로 관리한다.
  • 상태 변경으로 인한 불필요한 렌더링이 발생하지 않는다.
    상태를 구독한 컴포넌트만 리렌더링이 발생한다.
  • selector를 통해 캐싱 (기본적으로 값을 캐싱함)

Atom : 데이터 조각/상태 조각
Selector : 아톰에서 파생된 데이터 조각 / 데이터를 반환하는 순수 함수


사용법

1. RecoilRoot 연결

Recoil 을 시작하기 위해서는 index.tsx(또는 index.jsx)에서 렌더링하고 있는 rootRecoilRoot로 감싸줘야 한다.

마치 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>
);

2. 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 같은 역할)

이렇게 간단한 코드로 하나의 전역 상태를 만들 수 있다.


3. 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의 이름을 넣어주면 값을 추적해서 값을 변경할 수 있다.




출처

profile
개발자

0개의 댓글