본문 바로가기
JS/React 강의

[React] 28. 리덕스 툴킷 (Redux-toolkit) 사용하기

by GiraffePark 2024. 4. 20.

리덕스 툴킷의 장점

리덕스 사용을 편하게 해주는, Redux toolkit이 있습니다.

리덕스 툴킷 공식 사이트 : https://redux-toolkit.js.org/

 

리덕스의 문제
1. action.type에 오타가 생기는 문제
2. state가 많아질수록 파일이 길어지고 state 객체가 복잡해지며, 이때 state 객체의 중첩된 데이터를 실수로 바꾸는 문제

 

 

 

이 경우에 리덕스는 React Provider와 별 차이가 없어보입니다. 하지만, 리덕스는 대책이 있습니다.

리덕스의 대책
1. 리듀서를 여러 작은 리듀서 파일로 나누어서, 파일이 거대해지는 것을 막는다
2. Redux toolkit이라는 라이브러리를 사용한다. (react-redux 개발진이 추가로 만든 라이브러리)

Redux toolkit은 굉장히 편하고 좋은 해결책이 되어줍니다.

 

 

 

 


리덕스 툴킷의 설치 방법

# NPM
npm install @reduxjs/toolkit

# Yarn
yarn add @reduxjs/toolkit

터미널에서 @reduxjs/toolkit을 설치한 후에,

 

 

 

// package.json
"redux": "^4.2.0",

프로젝트의 package.json에 들어가서, redux 모듈을 제거합니다. 이미 redux-toolkit에 redux가 포함되어 있기 때문입니다.

 

 

 

 

 


리덕스 툴킷 - createSlice : state slice 추가하기

import { createStore } from "redux";

const initialState = {
  counter: 1,
  showCounter: true,
};


const counterReducer = (state = initialState, action) => {
  if (action.type === "increment") {
    return {
      counter: state.counter + 1,
      showCounter: state.showCounter,
    };
  }

  if (action.type === "increase") {
    return {
      counter: state.counter + action.amount,
      showCounter: state.showCounter,
    };
  }

  if (action.type === "decrement") {
    return {
      counter: state.counter - 1,
      showCounter: state.showCounter,
    };
  }

  if (action.type === "toggle") {
    return {
      showCounter: !state.showCounter,
      counter: state.counter,
    };
  }

  return state;
};

const store = createStore(counterReducer);

export default store;

저번 강의에서 만들었던 코드를 리덕스 툴킷으로 수정해봅시다.

 

 

 

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

리덕스툴킷에서 createSlice를 불러온 후,

 

 

 

createSlice({
  name: "counter",
  initialState: initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.payload; // redux-toolkit에서는 payload 값을 전달 받을 때, property 명을 payload로 지정해줘야 한다.
    },
    toggle(state) {
      state.showCounter = !showCounter;
    },
  },
});

createSlice()함수에 객체를 전달합니다.

 

createSlice() 안에 들어가는 객체의 구성

name : state의 식별자
initialState : state의 초기값
reducers: reducer 함수들이 담긴 object

 

기존 React-redux에서의 리듀서 함수 : counterReducer()라는 하나의 큰 리듀서 함수를 만든 후, action.type에 따라 state를 변경하도록 작성했습니다.

redux-toolkit에서의 리듀서 함수 : reducers 안에 각각 여러 개의 리듀서 함수들을 제작한 후, 전달합니다.

장점 : action.type은 String 타입으로 주어지다보니 오타가 발생할 수 있습니다. 리덕스 툴킷은 함수를 각각 선언할 수 있게 함으로써 IDE의 자동 완성 기능과 오타 점검 기능을 사용할 수 있습니다.

 

 

 

기존 React-redux에서의 리듀서 함수 

 if (action.type === "increment") {
    return {
      counter: state.counter + 1,
      showCounter: state.showCounter,
    };
  }

  if (action.type === "increase") {
    return {
      counter: state.counter + action.amount,
      showCounter: state.showCounter,
    };
  }

항상 state 객체를 복사한 후에, 새로 object에 값을 담은 후, return을 하는 구조였습니다.

 

 

redux-toolkit에서의 리듀서 함수 

    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },

반면에 리덕스 툴킷은 return할 필요 없이, 기존의 state를 바로 변경하면 됩니다.

그래서 '++' 같은 연산자를 사용해도 괜찮습니다.

 

 

 

장점 : 기존의 state를 직접적으로 변경하는 것은 Redux 규칙에 위배되는 행위입니다. 하지만, react-redux 내부에는 immer라는 패키지가 있습니다. immer는 state 변경 코드를 감지하고 자동으로 원래 있는 state를 복제합니다. 복제한 state 객체에 createSlice()의 리듀서 함수가 변경한 state를 적용시킨 다음에 return 해줍니다.

즉, immer 패키지가 알아서 기존의 번거로운 작업을 처리해주기 때문에, 개발자가 편하게 코딩할 수 있습니다. (== 백그라운드에서 자동으로 변환작업이 이루어집니다.)

 

 

 

 


리덕스 툴킷 - configureStore : state 연결하기

const counterSlice = createSlice({
  name: "counter",
  initialState: initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.payload; // redux-toolkit에서는 payload 값을 전달 받을 때, property 명을 payload로 지정해줘야 한다.
    },
    toggle(state) {
      state.showCounter = !showCounter;
    },
  },
});

위에서 작성한 createSlice 코드를 상수에 저장합니다.

 

 

 

const store = configureStore({
  reducer: counterSlice.reducer,
});

export default store;

store 상수를 선언하는데, configureStore()이라는 함수를 redux-toolkit에서 불러와서 연결해줍니다.

configureStore()에 위 구문처럼 reducer 함수들을 전달합니다.

 

- configureStore()은 redux-toolkit에 내장된 함수로, createSlice()로 만든 state들의 reducer 함수를 하나로 묶어줍니다.

- configureStore()에 객체를 인수로 전달합니다. 이 객체의 property로 reducer를 만든 후, 거기에 slice의 reducer를 넘겨줍니다. slice의 state가 여러 개일 경우, 위 코드와는 다른 방식으로 작성해줄 필요가 있는데, 추후에 이에 대해 설명하겠습니다.

 

마지막으로, 이 store 상수를 export 해주면, 프로젝트 전역에서 state를 접근할 수 있게 됩니다.

 

 

 

 


프로젝트 전역에서 리덕스 툴킷의 slice 접근하기

// store/index.js 에서
export const counterActions = counterSlice.actions;

{state이름}Actions라는 상수를 선언한 후, {state이름}.actions property를 전달합니다.

actions property 안에는 리듀서 함수에 대한 정보가 담겨 있습니다.

그리고 이걸 export합니다.

 

 

 

// Counter.js 에서
  const incrementHandler = () => {
    dispatch({ type: "increment" });
  };
  const decrementHandler = () => {
    dispatch({ type: "decrement" });
  };
  const increaseHandler = () => {
    dispatch({ type: "increase", amount: 10 });
  };

  const toggleCounterHandler = () => {
    dispatch({ type: "toggle" });
  };

이제 리액트 프로젝트 내부로 넘어옵니다.

기존에 Redux를 가지고, 위의 코드를 작성했었습니다. action.type이 담긴 action 객체를 전달하다보니, 오타가 발생해도 바로 알아챌 수 없는 구조입니다.

 

 

 

  const incrementHandler = () => {
    dispatch(counterActions.increment());
  };
  const decrementHandler = () => {
    dispatch(counterActions.decrement());
  };
  const increaseHandler = () => {
    dispatch(counterActions.increase(10));
  };

  const toggleCounterHandler = () => {
    dispatch(counterActions.toggle());
  };

이제 이렇게 바뀝니다.

counterActions를 import한 후, 그대로 리듀서 함수를 불러올 수 있습니다. 

 

 

 

 

// 기존 Redux
  const increaseHandler = () => {
    dispatch({ type: "increase", amount: 10 });
  };


// 리덕스 툴킷
  const increaseHandler = () => {
    dispatch(counterActions.increase(10));
  };

payload를 전달할 때에도, 방식의 차이가 나타납니다.

 

 

 

다음에도 리덕스 툴킷에 대해 추가적으로 알아보겠습니다.

 

 

반응형