Redux Toolkit 간단 사용법

김대은·2022년 8월 4일
1

Redux-Toolkit ?

Redux - Toolkit 은 Redux를 보다 편리하게 사용하기 위해
제공되어 지는 Redux 개발 도구 이다.

이는 redux-thunk를 기반으로 사용하지만, 그렇다고 해서
redux-saga 등 다른 미들웨어를 사용하지 못하는건 아니다.

// NPM
npm install @reduxjs/toolkit

// Yarn
yarn add @reduxjs/toolkit

ConfigureStore

import { createStore } from 'redux';
import rootReducer from './module/rootReducer';

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

기존에는 store를 생성할 때는 위와 같이 생성했다.

그런데 configureStore를 사용하면

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

const store = configureStore({ reducer: rootReducer });

이렇게 작성 하면 된다.
Redux-Toolkit 의 configureStore 는 Redux 의 createStore 를 활용한 API 로써,
위 처럼 reducer 필드를 필수적으로 넣어주어야 하며 default 로 redux devtool 을 제공한다.

createAction

툴킷 사용 전 기존 코드는 아래와 같다.

// Action Type
const MODE_REMOVE = 'REMOVE';
const MODE_SAVE = 'SAVE';
const MODE_SELECT_ROW = 'SELECT_ROW';

// Action Create Function
export const boardSave = (saveData) => ({
    type: MODE_SAVE,
    saveData: {
        boardId: saveData.boardId,
        boardTitle: saveData.boardTitle,
        boardContent: saveData.boardContent
    }
});
export const boardRemove = (boardId) => ({
    type: MODE_REMOVE,
    boardId: boardId
});
export const boardSelectRow = (boardId) => ({
    type: MODE_SELECT_ROW,
    boardId: boardId
});

위 코드를 createAtion을 사용하면 아래처럼 표현 할 수 있다.

// Action Type
const MODE_REMOVE = 'REMOVE';
const MODE_SAVE = 'SAVE';
const MODE_SELECT_ROW = 'SELECT_ROW';

// Action Create function
export const boardSave = createAction(MODE_SAVE, saveData => saveData);
export const boardRemove = createAction(MODE_REMOVE, boardId => boardId);
export const boardSelectRow = createAction(MODE_SELECT_ROW, boardId => boardId);

creatAtion은 type만 넣어주어도 스스로 type을 가진 action object를 생성해준다.
만약 이 생성함수를 호출할 때 parameter를 추가로 넣어준다면
이는 그대로 payload필드에 자동으로 들어가게 된다.

const MODE_INCREMENT = 'INCREMENT';
const increment = createAction(MODE_INCREMENT);

let action = increment(); // return { type: 'INCREMENT' }
let action = increment(5); // return { type: 'INCREMENT', payload: 5 }

createReducer

일반적으로 기존에 reducer를 사용할 때 switch문을 사용 하여
action 의 type을 구분해 특정 로직을 수행했다.

export default function boardReducer(state=initialState, action) {
    switch(action.type) { 
        case MODE_REMOVE:
            return { ...state, boards: state.boards.filter(row => row.boardId !== action.boardId) };
        case MODE_SAVE:
            if(action.saveData.boardId === '') { 
                return { lastId: state.lastId+1, boards: state.boards.concat({ ...action.saveData, boardId: state.lastId+1 }), selectRowData: {} };
            } else { 
                return { ...state, boards: state.boards.map(data => data.boardId === action.saveData.boardId ? {...action.saveData}: data), selectRowData: {} };
            }
        case MODE_SELECT_ROW:
            return { ...state, selectRowData: state.boards.find(row => row.boardId === action.boardId) };
        default:
            return state;
    }
};

이를 createReducer를 사용하면

export default createReducer(initialState, {
    [MODE_REMOVE]: (state, { payload: boardId }) => {
        return { ...state, boards: state.boards.filter(row => row.boardId !== boardId) }
    },
    [MODE_SAVE]: (state, { payload: saveData}) => {
        if(saveData.boardId === '') {
            return { lastId: state.lastId+1, boards: state.boards.concat({ ...saveData, boardId: state.lastId+1 }), selectRowData: {} }
        } else {
            return { ...state, boards: state.boards.map(data => data.boardId === saveData.boardId ? {...saveData} : data), selectRowData: {} }
        }
    },
    [MODE_SELECT_ROW]: (state, { payload: boardId }) => {
        return { ...state, selectRowData: state.boards.find(row => row.boardId === boardId) }
    }
})     

위 코드를 보면 알 수 있듯이 switch 문이 없어졌고,
createReducer 의 첫번 째 인자값인 initialState 가 default 값이기에 default 문 또한 필요없어졌다.

createSlice

createSlice({
    name: 'reducerName',
    initialState: [],
    reducers: {
        action1(state, payload) {
            //action1 logic
        },
        action2(state, payload) {
            //action2 logic
        },
        action3(state, payload) {
            //action3 logic
        }
    }
})

creatSlice의 기본적인 형태는 위와 같다.

name속성은 액션의 경로를 잡아줄 해당 이름을 나타낸다.
reducer는 이전에 사용하던 action의 구분을 주어 해당 action의 로직을 수행하는 방법과 동일하다.

차이점이라면 기존에는 action create함수 와 Action type을 선언해 사용했었다면,
creatSlice의 reducers에서는 이 과정을 건너뛰고 action을 선언하고
해당 action이 dispatch되면 바로 state를 가지고 해당 action을 처리한다.

즉, reducers 안의 코드들은 Action Type, Action Create Function, Reducer 의 기능이 합쳐져 있는 셈이다.

특징 : 불변성 관리

우리는 리액트를 하면서 불변성을 유지하기 위해 push등과 같은 기존 data에 영향이 가는 메서드 대신 state의 구조를 복사해 새로 생성해내는 concat과 같은 메서드를 사용하였다.
하지만 redux-toolkit의 createReducer와 creatSlice함수는
이러한 불변성까지 자동으로 관ㄹ니해주는 유틸을 가지고 있다.

이는 creatReducer와 createSlice는 immer라이브러리를 내재하고 있기 때문이다.
여기서 immer는 우리가 불변성을 신경쓰지 않도록 불변성 관리를 해주는 라이브러리 이다.

즉, 우리는 더이상 리듀서에서 새로운 state객체를 만들어 return할 필요가 없어지고 state를 직접 변경해도 된다는 말이다.

##특징 : Scope

만약 아래와 같은 코드가 있다고 가정해보자.

export default function testReducer(state=initialState, action) {
    switch(action.type) {
        case A_COLOR:
            let color='red'; //중복
            return;
        case B_COLOR:
            let color='blue'; //중복
            return;
        case C_COLOR:
            let color='yellow'; //중복
            return;
        default:
            return state;
    }
}

위 코드를 실행하면 변수명이 스코프내에서 중복된다고 에러가 나올 것이다.
이는 reducer가 기본적으로 함수 자체를 scope로 보기 때문이다.
반면 createReducer와 creatSlice는
각 action의 타입마다 코드블럭을 scope로 본다.

리덕스 툴킷은 반드시 리덕스를 사용해보고, 이해를 한 후 사용해보길 바란다.

profile
매일 1% 이상 씩 성장하기

0개의 댓글