본문 바로가기
React

Redux Toolkit

by 안자바먹지 2021. 6. 4.
728x90

 

Redux Toolkit

 

Redux Toolkit은 Redux를 더 편하게 사용할 수 있게 해주는 Redux에서 공식으로 제공하는 도구이다. 기존 Redux의 문제점이라고 할 수 있는 것들은 다음과 같이 생각해 볼 수 있다.

 

  1. 필요한 boilerplate가 많음
  2. 많은 패키지의 의존성 필요

 

Redux Toolkit을 사용하지 않고 기존처럼 구성한다면

 

  1. 액션 타입 정의
  2. 액션 함수 정의
  3. 리듀서 정의
  4. 상태값의 불변을 위한 immer 사용
  5. 비동기 처리를 위해 thunk 혹은 saga 사용
  6. 불필요한 렌더링을 막아주는 reselect 사용

등등 구성해야할 것이 상당히 많다. 하지만 Redux Toolkit을 사용한다면! saga를 제외한 (thunk는 기본적으로 내장되어 있음)  위의 기능들을 사용할 수 있다.

 

 

사용법

 

createAction

기존 방법

// 액션 정의
const ADD = 'todo/add';

// 액션 함수 정의
const add(todo) {
  return {
    type: ADD,
    payload: todo
  }
};

// 사용
const action = add({id:'test', title: '테스트'});

기존 방법의 경우 액션 함수를 정의하기 위해 별도로 액션을 정의해야 한다.

 

createAction 사용

// 액션 정의와 동시에 액션 함수 생성
const add = createAction('todo/add');

// 액션 사용
// {type: 'todo/add', payload: {id:'test', title: '테스트'}}
const action = add({id:'test', title: '테스트'}); 

createAction을 사용하면 인자로 type값만 넣어주었을 때 액션함수가 생성되고 액션 함수를 호출하면서 파라미터를 넘기면 payload로 들어간다. 

 


 

createReducer

기존 방법

const initialState = [];
const todoReducer = (state = initialState, action) => {
  swtich (action.type) {
    case 'add' :
      return [...state, action.payload];
    case 'delete' :
      return state.filter(e => e.id !== action.payload.id);
    default:
      return state;
  }
}

리듀서를 만들 때 조건문을 사용하여 action type에 따라 로직을 실행했다.

 

createReducer 사용

const initialState = [];
const todoReducer = createReducer(initialState, {
  add: (state, action) => {state.push(action.payload)},
  delete: (state, action) => state.filter(e => e.id !== action.payload.id),
});

별도의 조건문 없이 첫 번째 인자로 초기값을 넣고, 두 번째 인자는 타입별로 리듀서를 정의하면 된다.

단, mutable 하게 데이터를 변경하였을 경우 값을 리턴하면 안된다. 툴킷에 내장되어 있는 immer가 알아서 처리해주기 때문이다. immutable하게 데이터를 조작하였다면 기존처럼 값을 리턴해주자.

 

공식문서에서는 createReducer의 두 번째 인자로 Builder Callpack을 사용하라고 되어 있다.

 

const add = createAction('todo/add');
const delete = createAction('todo/delete');

const initialState = [];
const todoReducer = createReducer(initialState, (builder) => {
  builder
    .addCase(add, (state, action) => {
      state.push(action.payload);
    })
    .addCase(delete, (state, action) => state.filter(e => e.id !== action.payload.id))
    .addDefaultCase((state, action) => initialState)
});

 


createSlice

createSlice는 action과 reducer가 합쳐져 있는 형태를 가진 함수이다. Ducks 패턴을 사용할때 쓴다.

 

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

const initialState = [];

const todoSlice = createSlice({
  name: 'todo',
  initialState,
  reducers: {
    add: (state, action) => {
      state.push(action.payload);
    },
  },
});

// 액션 생성 함수
export const { add } = todoSlice.actions;
export default todoSlice.reducer;

 

createSlice의 name은 액션의 prefix부분이라고 생각하면 된다. (e.g. todo/add, todo/delete 에서의 todo 부분.)

initialState는 초기값, reducers는 기존에 액션생성함수와 액션 타입을 따로따로 선언해줬던 것을 한번에 정의했다고 생각하면 된다. 즉, dispatch(add({id : 'test', title : '테스트'})) 로 디스패치 하면 해당 부분을 찾아 액션을 처리하는 것이다.

 


configureStore

기존 방법

import {createStore} from 'redux';
import rootReducer from './Redux/reducer';

const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = createStore(rootReducer, devTools);

createStore를 사용하여 combineReducers로 묶인 rootReducer를 인자로 넣어 사용하고, devTools를 사용하기 위해 별도 설정을 또 해줘야 한다.

 

configureStore 사용

import { configureStore } from '@reduxjs/toolkit';
import todoReducer from '../features/todo/todoSlice';

export const store = configureStore({
  reducer: {
    todo: todoReducer,
  },
});

 

combineReducers를 사용하여 리듀서들을 묶어줄 필요가 없어진다. 또한 redux devtool을 기본으로 제공하기 때문에 별도 설정이 필요없다.

 


createAsyncThunk

기존 방법

기존에는 redux에서 비동기 처리를 할 경우 별도의 미들웨어 thunk나 saga 등을 사용했다. 또한 하나의 비동기 액션에 대해 start, success, fail 등의 상태를 각각 해줘야 했기 때문에 소스가 길어지게 된다.

 

createAsyncThunk

이름과 같이 별도 설정없이 thunk를 간편하게 사용할 수 있다. thunk외의 미들웨어를 사용할 경우 직접 구현하거나 별도의 패키지를 설치해야 한다. 그리고 액션에 대해 pending(호출 전), fulfilled(성공), rejected(실패)로 정의된 상태를 사용하면 된다.

 

const fetchTodo = createAsyncThunk(
  'todo/fetchTodo', // 액션 이름
  async () => { // 비동기 함수
    try {
      const {data} = await axios.get('~~~');
      return data;
    } catch (e) {
      console.log(e);
    }
  }
);

 

createAsyncThunk를 사용하면 액션 이름에 대해 pending, fulfilled, rejected 각 상태에 대한 action이 자동으로 생성된다. 생성된 action은 createSlice의 extraReducers에서 사용할 수 있다.

 

 

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

const initialState = {
  todo: [],
  loading: false,
  error: false,
};

const fetchTodo = createAsyncThunk(
  'todo/fetchTodo', 
  async () => {
    try {
      const {data} = await axios.get('~~~');
      return data;
    } catch (e) {
      console.log(e);
    }
  }
);

const todoSlice = createSlice({
  name: 'todo',
  initialState,
  reducers: {
    getTodos: (state, {payload}) => {
      state.todo = payload;
    },
  },
  extraReducers: {
    [fetchTodo.pending] : (state) => {state.loading = true},
    [fetchTodo.fulfilled] : (state, {payload}) => {
      state.todo = payload;
      state.loading = false;
      state.error = false;
    },
    [fetchTodo.rejected] : (state) => {
      state.loading = false;
      state.error = true;
    }
  }
});

// 액션 생성 함수
export const { add } = todoSlice.actions;
export default todoSlice.reducer;

 

728x90

'React' 카테고리의 다른 글

Controlled Component vs UnControlled Component  (0) 2021.05.17
useEffect  (0) 2021.02.02
useState의 이전 상태값  (0) 2020.12.28
useState의 비동기적 동작  (0) 2020.12.21
redux-promise  (0) 2020.12.18

댓글