[React] 리덕스 더 편하게 사용하기

2021. 6. 9. 16:06Front-end/React

728x90
반응형

이번시간에는 리덕스를 좀 더 편하게 사용하는 방법에 대해 알아보겠다.

  • redux-actions : 액션 생성 함수, 리듀서를 작성할 때 편하게 쓸 수 있게 해주는 라이브러리
  • immer

설치: yarn add redux-actions

reducx-actions

  • redux-actions를 사용하면 액션 생성 함수를 더 짧은 코드로 작성할 수 있다.
  • 리듀서를 작성할 때도 switch/case 문이 아닌 handleActions 함수를 사용하여 각 액션마다 업데이트 함수를 설정하는 형식으로 작성할 수 있다.
import { createAction } from "redux-actions";

// 액션 타입 정의하기
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

// 액션 생성 함수 정의하기
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

✅modules/counter.js

createAction을 사용하면 매번 객체를 직접 만들어줄 필요 없이 더욱 간단하게 액션 생성 함수를 선언할 수 있다.

 

// 초기 상태, 리듀서 함수 만들기
const initialState = {
  number: 0,
};

const counter = handleActions(
  {
    [INCREASE]: (state, action) => ({ number: state.number + 1 }),
    [DECREASE]: (state, action) => ({ number: state.number - 1 }),
  },
  initialState
);

export default counter;

✅modules/counter.js

handleActions 함수의 첫번째 파라미터에는 각 액션에 대한 업데이트 함수를 넣어주고 두번째 파라미터에는 초기 상태를 넣어준다.

 

똑같이 todos 모듈에도 적용한다.

그런데 todos 모듈에서는 액션 생성 함수에서 파라미터를 필요로 하고 있다.

createAction으로 액션을 만들면 액션에 필요한 추가 데이터는 payload라는 이름을 사용한다.

액션 생성 함수에서 받아온 파라미터를 그대로 payload에 넣는 것이 아니라 변형을 주어서 넣고 싶다면 createAction의 두 번째 함수에 payload를 정의하는 함수를 따로 선언해서 넣어 주면 된다.

 

import { createAction } from "redux-actions";

// 액션 타입 정의하기
const CHANGE_INPUT = "todos/CHANGE_INPUT";
const INSERT = "todos/INSERT";
const TOGGLE = "todos/TOGGLE";
const REMOVE = "todos/REMOVE";

// 액션 생성 함수 만들기
let id = 3;

export const changeInput = createAction(CHANGE_INPUT, (input) => input);
export const insert = createAction(INSERT, (text) => ({
  id: id++,
  text,
  done: false,
}));
export const toggle = createAction(TOGGLE, (id) => id);
export const remove = createAction(REMOVE, (id) => id);

✅modules/todos.js

insert의 경우 todo 객체를 액션 객체 안에 넣어 주어야 하기 때문에 두번째 파라미터에 text를 넣으면 todo 객체가 반환되는 함수를 넣어주었다.

 

// 초기 상태 정의, 리듀서 함수 만들기
const initialState = {
  input: "",
  todos: [
    {
      id: 1,
      text: "방 청소하기",
      done: true,
    },
    {
      id: 2,
      text: "리덕스 공부 끝내기",
      done: false,
    },
  ],
};

const todos = handleActions({
  [CHANGE_INPUT]: (state, action) => ({ ...state, input: action.payload }),
  [INSERT]: (state, action) => ({
    ...state,
    todos: state.todos.concat(action.payload),
  }),
  [TOGGLE]: (state, action) => ({
    ...state,
    todos: state.todos.map((todo) =>
      todo.id === action.payload ? { ...todo, done: !todo.done } : todo
    ),
  }),
  [REMOVE]: (state, action) => ({
    ...state,
    todos: state.todos.filter((todo) => todo.id !== action.payload),
  }),
},
initialState
);

export default todos;

✅modules/todos.js

마찬가지로 리듀서 함수도 위와 같이 handleActions를 사용하여 변경했다.

그런데 위와 같이 작성하면 action.payload가 무엇을 뜻하는지 알기 어려울 수도 있으므로 비구조화 할당 문법으로 payload의 이름을 새로 설정해주면 더 쉽게 파악할 수 있다.

 

// 초기 상태 정의, 리듀서 함수 만들기
const initialState = {
  input: "",
  todos: [
    {
      id: 1,
      text: "방 청소하기",
      done: true,
    },
    {
      id: 2,
      text: "리덕스 공부 끝내기",
      done: false,
    },
  ],
};

const todos = handleActions(
  {
    [CHANGE_INPUT]: (state, { payload: input }) => ({ ...state, input }),
    [INSERT]: (state, { payload: todo }) => ({
      ...state,
      todos: state.todos.concat(todo),
    }),
    [TOGGLE]: (state, { payload: id }) => ({
      ...state,
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      ),
    }),
    [REMOVE]: (state, { payload: id }) => ({
      ...state,
      todos: state.todos.filter((todo) => todo.id !== id),
    }),
  },
  initialState
);

export default todos;

✅modules/todos.js

 

immer

리듀서에서 상태를 업데이트할 때는 불변성을 지켜야 하기 때문에 앞에서는 spread 연산자(...)와 배열의 내장 함수를 활용했다.

그러나 모듈의 상태가 복잡해질 수록 불변성을 지키기가 까다로워진다.

따라서 모듈의 상태를 설계할 떄에는 객체의 깊이가 너무 깊어지지 않도록 주의해야 한다.

객체의 구조가 복잡해지거나 객체로 이루어진 배열을 다룰 경우 immer를 사용하면 훨씬 편리하게 상태를 관리할 수 있습니다.

  • 설치: yarn add immer
const todos = handleActions(
  {
    [CHANGE_INPUT]: (state, { payload: input }) =>
      produce((state, draft) => {
        draft.input = input;
      }),
    [INSERT]: (state, { payload: todo }) =>
      produce((state, draft) => {
        draft.todos.push(todo);
      }),
    [TOGGLE]: (state, { payload: id }) =>
      produce((state, draft) => {
        const todo = draft.todos.find((todo) => todo.id === id);
        todo.done = !todo.done;
      }),
    [REMOVE]: (state, { payload: id }) =>
      produce((state, draft) => {
        const index = draft.todos.findIndex((todo) => todo.id === id);
        draft.todos.splice(index, 1);
      }),
  },
  initialState
);

export default todos;

✅modules/todos.js

immer를 사용한다고 해도 모든 업데이트 함수에 immer를 적용할 필요는 없다.

일반 자바스크립트로 처리하는 것이 더 편할 때는 immer를 적용하지 않아도 된다.

728x90
반응형