Redux 사용을 보다 간편하게 해주는 Redux Toolkit

2022. 9. 7. 01:55Front-end

Redux Toolkit

Redux Toolkit은 Redux를 더 쉽게 사용하기 위해 만들어졌다. 리덕스는 Flux 아키텍처를 기반으로 잘 설계된 라이브러리이지만 다음과 같은 문제점이 있었다.

  • 리덕스의 복잡한 스토어 설정
  • 리덕스를 유용하게 사용하기 위해서 필요한 많은 패키지들
  • 리덕스 사용을 위해 요구되는 다량의 상용구(boilerplate) 코드들

 

이러한 문제점을 개선하기 위해 Redux Toolkit이 만들어지게 되었다. 리덕스 툴킷에서 제공하는 주요 함수들을 사용하면 기존 리덕스의 복잡도를 낮추고 사용성을 높여서 코드를 작성할 수 있다.

 

Getting Started | Redux Toolkit

 

redux-toolkit.js.org


Redux Toolkit 설치

$ yarn add @reduxjs/toolkit

혹은 npm install @reduxjs/toolkit


configureStore

configureStore 함수는 리덕스 라이브러리의 createStore 함수를 추상화한 것으로, 기존의 번거로웠던 리덕스 설정을 간편하게 할 수 있도록 해주고 설정 시 기본으로 redux-thunk와 DevTools를 제공해준다.

 

다음과 같이 index.js, store/config.js 파일을 작성해준다.

/* index.js */

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import { Provider } from 'react-redux';
import store from './store/config';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
/* store/config.js */

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { createLogger } from 'redux-logger';
import counterSlice from './slices/counterSlice';
import todoSlice from './slices/todoSlice';
import userSlice from './slices/userSlice';

const logger = createLogger();

const rootReducer = combineReducers({
  counter: counterSlice.reducer,
  todo: todoSlice.reducer,
  user: userSlice.reducer
});

const initialState = {};

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
  preloadedState: initialState,
  enhancers: (defaultEnhancers) => [...defaultEnhancers]
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = () => useDispatch<AppDispatch>();

export default store;

configureStore 함수에 전달된 파라미터 객체에 대해 정리하면 다음과 같다.

 

reducer

  • 리덕스 스토어의 rootReducer를 설정
  • combineReducers 함수를 사용하여 slice reducer들을 병합한 rootReducer를 설정 가능
  • 단일 함수로 설정한 경우엔 스토어의 rootReducer로 사용
  • slice reducer로 설정한 경우엔 자동으로 combineReducers에 전달하여 rootReducer를 생성

 

middleware

  • redux-logger와 같은 리덕스 미들웨어를 설정
  • 미들웨어를 설정한 경우엔 자동으로 applyMiddleware에 전달
  • 미들웨어를 설정하지 않은 경우엔 getDefaultMiddleware를 호출

 

preloadedState

  • 리덕스 스토어의 초기값 설정

 

enhancers

  • 사용자 정의 미들웨어를 설정 콜백 함수로 설정하면 미들웨어 적용 순서를 정의 가능

 

configureStore 함수의 파라미터에 대한 자세한 내용은 공식문서를 통해 확인할 수 있다.

 

configureStore | Redux Toolkit

 

redux-toolkit.js.org

그리고 useAppSelector와 useAppDispatch는 기존의 useSelector와 useDispatch hooks를 추상화한 것으로, 각 컴포넌트에서 useSelector나 useDispatch를 매번 설정하지 않고 애플리케이션 전역에서 사용이 가능하다.


createSlice

createSlice 함수는 선언한 slice의 name에 따라서 액션 생성자, 액션 타입, reducer를 자동으로 생성해주기 때문에 별도로 createAction이나 createReducer를 사용하지 않아도 된다.

 

다음과 같이 store/slices/counterSlice.js, store/slices/todoSlice.js 파일을 작성해준다.

/* store/slices/counterSlice.js */

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

interface CommonState {
  value: number
}

const initialState: CommonState = {
  value: 0
};

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    setCounter(state, action: PayloadAction<number>) {
      state.value = action.payload;
    }
  }
});

export const { setCounter } = counterSlice.actions;

export default counterSlice;
/* store/slices/todoSlice.js */

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

interface TodoItem {
  id: number,
  title: string,
  checked: boolean
}

export interface CommonState {
  todoList: TodoItem[]
}

const initialState: CommonState = {
  todoList: []
};

export const todoSlice = createSlice({
  name: 'todo',
  initialState,
  reducers: {
    setTodo(state, action: PayloadAction<TodoItem[]>) {
      state.todoList = action.payload;
    }
  }
});

export const { setTodo } = todoSlice.actions;

export default todoSlice;

createSlice 함수를 사용하여 slice를 작성하면 기존의 리덕스 라이브러리로 reducer를 구성했을 때 비해서 코드가 많이 줄어든 것을 확인할 수 있다.

 

slice를 생성할 때 액션 타입은 name이 앞에 붙은 형태(counter/setCounter, todo/setTodo)로 생성되고, 생성된 액션 타입을 가진 액션이 dispatch되면 reducer가 실행된다.

반응형