[#1] redux-toolkit-todo

undefcat·2021년 4월 29일
2

redux-toolkit-todo

목록 보기
1/4
post-thumbnail

redux-toolkit-todo

Redux Toolkit은 마치 react-hook처럼, 기존 Redux의 불편함을 해소하고자 나온 또다른 redux 라이브러리입니다.

메인 화면에서도 볼 수 있듯, efficient Redux development입니다. 저는 React와 Redux를 프로덕션 레벨에서 사용해 본 경험이 없기 때문에 뭐라 말하기 그렇지만, Redux는 튜토리얼에서도 많은 코드들을 작성해야만 하는 불편함이 있습니다.

  • 하나의 저장소인 store를 만듭니다.
  • store를 구성하는 state들(혹은 reducer)을 정의합니다.
  • state를 변경하는 action을 정의합니다.
  • action을 구분하기 위한 action type constant를 정의합니다.
  • 해당 액션을 생성하는 action creator를 정의합니다.
  • action에따라 state를 변경하는 reducer를 정의합니다.

즉, 어떤 하나의 행동을 위해서 action type constant, action creator, reducer 를 각각 다 만들어야 합니다.

물론 이런 기본 흐름은 Redux-Toolkit에서도 변하지 않았습니다. 하지만 이런 코드들을 좀 더 쉽고 빠르고 간단하게 효과적으로 도와주는 것이 Redux-Toolkit의 장점이라 할 수 있습니다.

저번 react-recoil-todo 시리즈에서 만들었던 똑같은 TodoApp을 또 만들어볼겁니다.

단, 이번엔 전역상태관리를 Redux-Toolkit을 이용해서 해볼 겁니다. 또한 recoil에서 이미 TodoApp의 기능적인 부분들은 설명을 했기 때문에, 이번 시리즈에서는 툴킷 API에 집중하도록 하겠습니다.

준비

다음과 같이 개발 환경을 구성합니다.

$ npx create-react-app redux-toolkit-tutorial
$ cd redux-toolkit-tutorial
$ npm install react-redux @reduxjs/toolkit todomvc-app-css
$ npm start

src 디렉터리에서 App.js, index.js를 제외한 모든 파일을 삭제합니다.

그 다음, src/store.js을 아래와 같이 생성합니다.

// src/store.js

import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
  reducer: {},
})

configureStore는 기존 Redux의 store를 쉽게 설정해주는 함수입니다. 우리는 간단한 TodoApp을 만들기 때문에 복잡한 설정은 필요 없습니다.

reducerstore의 상태를 구성합니다. 이전 Recoil에서 atom으로 상태를 만들었다면, Redux에서는 reducer로 상태를 구성한다고 생각하셔도 무방합니다.

createSlice

우선 src/state/todos.js를 아래와 같이 생성합니다.

// src/state/todos.js

import { createSlice } from '@reduxjs/toolkit'

let uniqId = 0

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    filterType: 'all',
    items: [],
  },

  reducers: {
    add: {
      reducer: (state, action) => {
        state.items.push(action.payload)
      },

      prepare: text => {
        return {
          payload: {
            id: ++uniqId,
            done: false,
            text,
          },
        }
      },
    },
  },
})

export const { add } = todosSlice.actions

export default todosSlice.reducer

그리고 다시 src/store.js 에서 다음과 같이 코드를 추가해줍니다.

// src/store.js

import { configureStore } from '@reduxjs/toolkit'
// 👇👇👇
import todos from './state/todos'

export default configureStore({
  reducer: {
    // 👇👇👇
    todos,
  },
})

하나 하나 설명해보도록 하겠습니다.

createSlice

기존 Redux에서는 action type constant, action creator, reducer를 다 따로 구현해야 했습니다. 이를 한방에 해주는게 createSlice입니다.

예전에 작성했던 코드는 보통 이러했습니다.

// state/todos/action-types.js
export const ADD_TODO = 'todo/add'
export const REMOVE_TODO = 'todo/remove'

// state/todos/actions.js
import { ADD_TODO, REMOVE_TODO } from './action-types'

export const addTodo = text => ({
  type: ADD_TODO,
  payload: text,
})

export const removeTodo = id => ({
  type: REMOVE_TODO,
  payload: id,
})

// state/todos/index.js
import { ADD_TODO, REMOVE_TODO } from './action-types'

// reducer
const todos = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      const text = action.payload
      const todo = { id: uniqId(), done: false, text }
      
      return [
        ...state,
        todo,
      ]
    
    case REMOVE_TODO:
      const id = action.payload
      
      return state.filter(todo => todo.id !== id)
      
    default:
      return state
  }
}

export default todos

일반적으로 하나의 action이 하나의 reducer와 대응하는데, createSlice에서는 reducer만 정의하면 action type, action creator는 모두 아래와 같이 한번에 해결됩니다.

// src/state/todos.js

// 맨 하단
export const { add } = todosSlice.actions

여기서 addaction creator이며, 이를 호출하면 기존에 Redux에서 만들었던 action creator와 비슷하게 typepayload로 이루어진 객체가 리턴됩니다.

이 때, typecreateSlice를 정의할 때 name값과 reducerskey값이 /로 연결된 값이 됩니다. 즉, 이 경우 todos/add입니다.

const todosSlice = createSlice({
  // 👇 `name`
  name: 'todos',
  initialState: {
    filterType: 'all',
    items: [],
  },

  reducers: {
    // 👇 `add`
    add: {
      reducer: (state, action) => {
        state.items.push(action.payload)
      },

      prepare: text => {
        return {
          payload: {
            id: ++uniqId,
            done: false,
            text,
          },
        }
      },
    },
  },
})

export const { add } = todoSlice.actions

console.log(add('Hello')) // { type: 'todos/add', payload: { id: 1, done: false, text: 'Hello' }

initialState

reducer의 기본 state값을 정의합니다. reducer에서 (state, action) => ...state가 바로 최초 initialState입니다. actiondispatch될 때마다 reducer가 동작하여 계속 state를 바꾸는 식으로 Redux는 작동합니다.

reducer, prepare

preparepayload를 한 번 거치는 미들웨어 같은 함수입니다. actiondispatch할 때, action은 매개변수를 받는데, 이 매개변수의 값을 전처리 하는 역할을 합니다.

prepare가 없는 형태가 일반적입니다.

reducers: {
  add: {
    reducer: (state, action) => {
      // ...
    },
    prepare: text => {
      // ...
    },
  },
    
  filter: (state, action) => {
    state.filterType = action.payload
  },
}

action의 매개변수를 전처리해야 한다면 reducerprepare를 정의하고, 그렇지 않다면 기본값이 reducer 이므로 그대로 정의하면 됩니다.

immer

리액트 및 리덕스에서는 기본적으로 상태를 immutabilty로 관리해야 합니다. 객체의 불변성에 관한 내용은 다른 좋은 포스팅 글인 링크를 참고하시면 되는데, 리액트와 Vue의 가장 큰 차이점이 바로 객체의 불변성을 관리하는 방식이었습니다.

리액트는 개발자가 직접 코드로 불변성을 관리해야합니다. 하지만 Vue는 객체의 setter를 프록싱하여 라이브러리가 불변성을 관리해주었습니다. immer가 하는 일이 바로 그런 일입니다.

redux-toolkit은 immer가 내장되어 있으므로 객체 불변성 코드를 굳이 작성하지 않아도 됩니다. 현재 작성된 add 액션의 reducer를 보면

reducers: {
  add: {
    reducer: (state, action) => {
      // 👇👇👇
      state.items.push(action.payload)
    },
  // ...
  },
},

state.itemspush 메서드로 변경하고 있습니다. 전통적인 redux에서는

const todo = { id: uniqId(), done: false, text }

return [
  ...state,
  todo,
]

위와 같이 state를 불변성으로 관리했습니다. immer가 내장되어 있으므로 redux-toolkit에서는 코드를 좀 더 수월하게 작성할 수 있습니다.

정리

redux-toolkit에 대해 간략히 정리해보았습니다. createSlice를 이용하여 코드를 좀 더 쉽고 빠르게 작성할 수 있고, immer의 내장기능으로 불변성 관리도 수월하게 할 수 있다는 점이 redux-toolkit의 가장 큰 장점이 아닐까 합니다.

다음 포스팅에서는 TodoApp에서 사용되는 action들을 미리 정의하고, 기본 컴포넌트를 구현해보도록 하겠습니다.

profile
undefined cat

0개의 댓글