웹개발/react

[React] Redux Toolkit

쌀쌀 2024. 2. 16. 19:01

리덕스 툴킷(Redux Toolkit)이란?

(1) 리덕스 툴킷

  • 리덕스를 개량한 것
  • 리덕스를 사용하기 위해 작성했던 ducks 패턴의 요소들이 지나치게 복잡하여 이를 보다 편하게 쓰기 위해 만든 것
  • 줄여서 RTK라고도 한다.

일반 리덕스와 코드 비교

(1) 툴킷 설치하기

  • CRA를 통해 새로운 프로젝트를 생성하고, yarn을 통해 아래 패키지를 설치한다.
yarn add react-redux @reduxjs/toolkit

 

(2) count 프로그램 코드 비교

 

<일반리덕스>

// 일반 리덕스 예시 코드

// Action Value
const ADD_NUMBER = "ADD_NUMBER";
const MINUS_NUMBER = "MINUS_NUMBER";

// Action Creator
export const addNumber = (payload) => {
  return {
    type: ADD_NUMBER,
    payload,
  };
};

export const minusNumber = (payload) => {
  return {
    type: MINUS_NUMBER,
    payload,
  };
};

// Initial State
const initialState = {
  number: 0,
};

// Reducer
const counter = (state = initialState, action) => {
  switch (action.type) {
    case ADD_NUMBER:
      return {
        number: state.number + action.payload,
      };
    // [퀴즈 답]
    case MINUS_NUMBER:
      return {
        number: state.number - action.payload,
      };
    default:
      return state;
  }
};

// export default reducer
export default counter;

 

<리덕스툴킷>

Action Value와 Action Creator를 직접 생성해주지 않고,

Action Value, Action Creator, Reducer가 하나로 합쳐짐

// src/redux/modules/counterSlice.js

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

const initialState = {
  number: 0,
};

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    addNumber: (state, action) => {
      state.number = state.number + action.payload;
    },

    minusNumber: (state, action) => {
      state.number = state.number - action.payload;
    },
  },
});

// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;

 

 

슬라이스만 자세히 살펴보자. 

슬라이스는 createSlice라는 API를 통해 만들 수 있으며 그 인자로 설정 정보를 객체로 받는다. 

우리가 필수로 작성해줘야 하는 값은 name, initialState, reducers가 있다.

//createSlice API 뼈대

const counterSlice = createSlice({
	name: '', // 이 모듈의 이름
	initialState : {}, // 이 모듈의 초기상태 값
	reducers : {}, // 이 모듈의 Reducer 로직
})

 

 

이렇게 하면 counterSlice 리듀서 객체 안에서 만들어주는 함수가 리듀서의 로직이 되면서 동시에 Action Creator가 되고, Action Value 까지 함수의 이름을 따서 자동으로 만들어진다.

// counterSlice.js의 Slice 구조

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    // 리듀서 안에서 만든 함수 자체가 리듀서의 로직이자, 액션크리에이터가 된다.
    addNumber: (state, action) => {
      state.number = state.number + action.payload;
    },

    minusNumber: (state, action) => {
      state.number = state.number - action.payload;
    },
  },
});

 

 

그리고 우리가 일반 리덕스에서 export를 통해서 각각 Action Creator를 내보내주었던 것을 아래 코드로 작성하면 똑같이 내보낼 수 있다.

// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;

 

(3) configStore 비교

 

<일반리덕스>

// 일반 리덕스 combineReducers 예시 코드

import { createStore } from "redux";
import { combineReducers } from "redux";
import counter from "../modules/counter";

const rootReducer = combineReducers({
  counter,
});
const store = createStore(rootReducer);
export default store;

 

<리덕스 툴킷>

// src/redux/modules/config/configStore.js

import { configureStore } from "@reduxjs/toolkit";
/**
 * import 해온 것은 slice.reducer 입니다.
 */
import counter from "../modules/counterSlice";
import todos from "../modules/todosSlice";

/**
 * 모듈(Slice)이 여러개인 경우
 * 추가할때마다 reducer 안에 각 모듈의 slice.reducer를 추가해줘야 합니다.
 *
 * 아래 예시는 하나의 프로젝트 안에서 counter 기능과 todos 기능이 모두 있고,
 * 이것을 각각 모듈로 구현한 다음에 아래 코드로 2개의 모듈을 스토어에 연결해준 것 입니다.
 */
const store = configureStore({
  reducer: { counter: counter, todos: todos },
});

export default store;

 

 

index.js Provider에 주입하는 것은 바뀐 게 없다

// index.js

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/config/configStore";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

 

 

사용하는 방식 또한 동일하다

// src/App.js

import React from "react";
import { useSelector } from "react-redux";

const App = () => {
  // Store에 있는 todos 모듈 state 조회하기
  const todos = useSelector((state) => state.todos);

  // Store에 있는 counter 모듈 state 조회하기
  const counter = useSelector((state) => state.counter);

  return <div>App</div>;
};

export default App;

 

파일 구조