Redux Toolkit

Redux를 사용하는 사람들이 많아지면서 redux를 사용하기위한 코드의 양이 많다는 불만이 나와 만들어진 것이 redux toolkit이다.

yarn add @reduxjs/toolkit

위 커맨드로 project에 redux toolkit 모듈을 설치한다. 여기서 왜 redux toolkit을 진행하는 이유는 redux의 동작 방식이나 이해가 안된다면 toolkit을 사용하기에 무리한 면이 있기 때문에 redux를 이해한 사람들이 좀더 확장하여 사용하기 위해서 toolkit을 사용하게 되는것이다.


createAction

createAction은 오브젝트를 반환했던 함수와 거의 동일한 동작을 한다.

import { createStore } from "redux";

const ADD = "ADD";
const DELETE = "DELETE";
const STORE = "store";

export const getAddAction = (text) => ({ type: ADD, id: Date.now(), text });
export const getDeleteAction = (id) => ({ type: DELETE, id });

const getItem = () => JSON.parse(storage.getItem(STORE));
const setItem = (item) => storage.setItem(STORE, JSON.stringify(item));

const reducer = (state = [], { type, ...data }) => {
  switch (type) {
    case ADD:
      setItem([data, ...state]);
      return getItem();
    case DELETE:
      setItem(state.filter(({ id }) => id !== data.id));
      return getItem();
    default:
      return getItem();
  }
};

const storage = window.localStorage;
const store = createStore(reducer);

export default store;

createAction을 쓰기 이전에는 action type key를 선언하고 action으로 전달할 데이터를 반환하는 함수도 선언하여 reducer에 사용해야 했었다. 이후 createAction은 이 두가지를 모두 합쳐 action object를 만들어주는 기능을 한다.

export const getAddAction = createAction("ADD");
export const getDeleteAction = createAction("DELETE");

처음 createAction에 제공되는 인수는 getAddAction.type해서 접근할 수 있는 데이터이고, getAddAction()에 들어가는 인수는 getAddAction.payload의 값이 된다. 그리고 createAction을 사용하여 코드를 다음과 같이 줄일 수 있다.

import { createStore } from "redux";
import { createAction } from "@reduxjs/toolkit";

const STORE = "store";

export const getAddAction = createAction("ADD");
export const getDeleteAction = createAction("DELETE");

const getItem = () => JSON.parse(storage.getItem(STORE));
const setItem = (item) => storage.setItem(STORE, JSON.stringify(item));

const reducer = (state = [], { type, payload }) => {
  switch (type) {
    case getAddAction.type:
      setItem([{ id: Date.now(), text: payload }, ...state]);
      return getItem();
    case getDeleteAction.type:
      setItem(state.filter(({ id }) => id !== payload));
      return getItem();
    default:
      return getItem();
  }
};

const storage = window.localStorage;
const store = createStore(reducer);

export default store;

createAction로 생성한 변수는(getAddAction) reducer에서 해당하는 키로 가게 되면 getAddAction를 호출하여 같이 전달한 인수에 맞게 payload를 reducer 안에서 사용하면 된다.

여기서 createAction함수는 modernJS의 클로져로 정의된 함수이다. 클로져로 정의된 createAction함수는 아래 코드로 정의 되어 있다. 코드를 보이는거와 같이 createAction함수 안에 actionCreator라는 함수가 정의되어 있는데 actionCreator에 대한 property가 정의되고 createAction는 actionCreator를 return한다. 그리고 우리는 return된 actionCreator를 변수에 담아 actionCreator.type에 접근할 수 있게 된다.

function createAction(type, prepareAction) {
  function actionCreator(...args) {
    if (prepareAction) {
      let prepared = prepareAction(...args);
      if (!prepared) {
        throw new Error("prepareAction did not return an object");
      }

      return {
        type,
        payload: prepared.payload,
        ...("meta" in prepared && { meta: prepared.meta }),
        ...("error" in prepared && { error: prepared.error }),
      };
    }
    return { type, payload: args[0] };
  }

  actionCreator.toString = () => `${type}`;
  actionCreator.type = type;
  actionCreator.match = (action) => action.type === type;

  return actionCreator;
}

createReducer

createReducer는 기존 reducer을 대체하는 방법이다. createReducer 두번째 인자에 등록할 함수 mapObject 형식으로 등록하면 된다. 아래는 mapObject로 등록한 createReducer을 정의한 코드이다.