[Redux Toolkit] Redux를 더 간편하게

windowook·2024년 7월 29일
post-thumbnail

🌱 사용하는 이유

Redux Toolkit은 기존에 Redux를 사용하면서 불편했던, 번거로웠던 사용법을 편하게 바꿔주는
라이브러리라고 생각하면 됩니다.
Redux는 리액트에서는 상위 컴포넌트에서 하위 컴포넌트로 props drilling을 통해 상태를 계속 전달할 수 있는데, 컴포넌트의 수가 일일이 감당하기 힘들 정도로 많은 프로젝트에서 계속 prop으로 전달할 경우
상태가 일으키는 버그가 발생할 경우 원점을 찾기도 힘들고 변경하는 것도 복잡해질 수 있습니다.
따라서 리액트는 상태 관리를 하는 게 매우 중요하죠. 규모가 클 수록 복잡도는 기하급수적으로 올라갑니다.

따라서 Redux같은 전역 상태 관리 라이브러리를 사용해서 store, reducer를 이용한 상태 저장과
상태 공유를 통해 상태를 효율적으로 관리합니다. Redux말고도 MobX, Recoil과 같은 라이브러리도 있죠.
하지만 대중적으로 가장 많이 사용되는 건 아무래도 Redux입니다. 특히 한국에서는 대다수의 기업이 사용하죠.

그래서 Redux는 전역 상태 관리를 위해 사용되는 라이브러리지만, 문법적으로 좀 번거롭고
코드가 길어지는 단점이 있습니다. 이러한 단점을 커버하고 사용의 편의성을 좀 더 갖춘 라이브러리가 바로
Redux Toolkit입니다. 오늘은 Redux Toolkit이 Redux보다 어떻게 더 편한지,
그리고 어떻게 사용할 수 있는지 알아보도록 하겠습니다.

Redux의 문법적 복잡함

import React from 'react';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';

function reducer(state, action) {
  if (action.type === 'up') {
    return { ...state, value: state.value + action.step };
  }
  return state;
}

const initialState = { value: 0 };
const store = createStore(reducer, initialState);

function Counter() {
  const dispatch = useDispatch();
  const count = useSelector((state) => state.value);
  return (
    <div>
      <button
        onClick={() => {
			dispatch({ type: 'up', step: 2 });
        }}
      >
        +
      </button>{' '}
      {count}
    </div>
  );
}

export default function App() {
  return (
    <Provider store={store}>
      <div>
        <Counter></Counter>
      </div>
    </Provider>
  );
}

Redux는 reducer, initialState, store를 생성하고 dispatch, selector를 이용하여
상태를 변경하고 새로운 상태를 반환해줍니다.

reducer
리듀서는 상태와 액션을 인자로 받아서 해당 상태의 value에 전달받은 액션의 타입에 따른 step을 계산해주는 방식으로 상태를 변경하는, 혹은 새로운 상태를 반환해주는 함수라고 생각하면 됩니다.

initialState
단어 그대로 초기 상태값을 의미합니다. 객체 안에 value:로 초기값을 설정해주면 됩니다.

store
애플리케이션에서 전역 상태를 저장하고 관리하는 객체입니다.
createStore라는 함수를 통해서 생성할 수 있고, reducer와 initialState를 전달받습니다.

dispatch
action의 타입과 스텝을 전달받아서 reducer에게 전달합니다.
reducer는 dispatch의 타입에 맞는 계산을 스텝만큼 실행합니다.

selector
셀렉터는 전체 상태에서 필요한 부분을 선택합니다. 코드의 state.value는 reducer의 value,
즉 상태를 선택한 것과 같다.

Redux는 이렇게 세팅해줘야 하는 값들이 많습니다^^ 예시 코드는 간단한 편이고
실제로 사용하면 action.type 별로 switch case문이나 if문을 사용해서 return 객체에
state를 복제하면서 불변성을 유지하며 상태를 어떻게 변경해서 새로 반환할지
일일이 명시해줘야 하는 번거로움이 있습니다.

이러한 특징은 가독성이나 실제 사용성에서 아쉬운 점이라고 할 수 있는데, 이걸 간단하게 설정하고
코드의 가독성도 좋게 만들어 주는 역할을 하는 Redux Toolkit이 그래서 등장했습니다.

🌱 특징과 사용법

Redux Toolkit을 사용하면 하나의 store에 모든 상태를 저장하는 대신,
slice를 이용해서 작은 store처럼 나눠서 상태를 저장할 수 있습니다.
slice들은 다시 Redux Toolkit이 하나의 store로 합쳐 생성해줍니다. 한 눈에 보기 더 간결해지죠.

createSlice

import { createSlice } from '@reduxjs/toolkit';

// slice = 작은 하나의 Store
const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: { value: 0 },
  reducers: {
    up: (state, action) => {
      console.log(action);
      state.value = state.value + action.payload;
    },
  },
});

export default counterSlice;
export const { up } = counterSlice.actions;

slice는 작은 store 역할을 하는 객체입니다. 필수적으로 포함되어야할 값은 다음과 같습니다.

  • name : slice의 이름을 지정한다. 아무거나 해도 상관없고, 보통은 변수명과 통일시킵니다.
  • initialState : store의 초기값과 동일합니다. 마찬가지로 객체에 value를 담습니다.
  • reducers : 리듀서를 타입별로 함수를 정해줄 수 있습니다.
    일반 Redux에서 조건문을 사용해서 판단하는 것과 달리 매우 간결합니다.

리듀서에 존재하는 액션 함수를 다른 컴포넌트에서 간결하게 사용할 수 있도록 export해 줄 수도 있습니다.

configureStore

// 거대한 store
import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
});
  • reducer : counterSlice 안에 있는 리듀서'들'이 있다면, 그 여러 개의 reducer를 하나의 reducer로 만들어 줍니다.
    그래서 실질적으로 한 개의 slice 안에 정의된 reducer를 다시 하나의 큰 reducer로 합쳐서
    여러 개의 slice reducer들이 store에 모이게 되는 구조입니다.

Redux Toolkit을 사용하는 코드로 수정된 코드

import React from 'react';
import { Provider, useSelector, useDispatch } from 'react-redux';
import store from './store';
import { up } from './counterSlice';

function Counter() {
  const dispatch = useDispatch();
  const count = useSelector((state) => {
    return state.counter.value;
  });
  return (
    <div>
      <button
        onClick={() => {
          dispatch(up(2));
        }}
      >
        +
      </button>{' '}
      {count}
    </div>
  );
}

export default function App() {
  return (
    <Provider store={store}>
      <div>
        <Counter></Counter>
      </div>
    </Provider>
  );
}

slice의 up 리듀서(액션 함수)와 dispatch의 up을 보면 알겠지만,
액션을 직접 만들지 않아도 Toolkit이 자동으로 action create으로 액션을 생성해줍니다.
그래서 action create을 통해 만들어진 액션을 사용한다면 step을 전달하는 대신 payload 값을 전달해줘야 합니다.

콘솔에 액션을 출력해보면, 객체안에 타입과 페이로드가 들어있는 것을 확인할 수 있습니다.

정리

Redux Toolkit으로 액션과 불변성을 지키며 복제 후 반환하는 상태를 일일이 명시하는 번거로움을 줄이고
store와 slice의 파일 분리를 통해서 컴포넌트에 코드 길이도 줄이면서 가독성도 끌어올릴 수 있는
큰 장점이 있습니다. 개인적으로 Redux는 극혐이고 Zustand나 Redux Toolkit이 마지노선이 아닐까..싶네요^

profile
안녕하세요

0개의 댓글