리덕스를 사용하다 보면, 느끼는 것이 있습니다.

  • 잘 사용하기 위해서는 너무 복잡한 구성을 필요로 합니다.
  • 복잡한 구성 만큼이나 많은 미들웨어와 추가적인 패키지를 설치해야 됩니다.
  • 보일러플레이트 코드가 과도하게 많이 사용 됩니다.

위 문제점을 toolkit이 완전하게 해결 하지는 못하지만, redux를 사용하는 사용자들의 불편을 완화 시켜 준다고 합니다.
줄여서 RSK 라고 부릅니다.

설치

yarn add @reduxjs/toolkit

or 

npm install @reduxjs/toolkit

toolkit에 포함된 도구들

  • configureStore() : createStore 를 통한 복잡한 설정을 단순화 하고, redux devtools 설정을 추가 해주었습니다.
  • createReducer() : switch 문을 작성하지 않고, case reduce 처리. state의 불변 업데이트 지원.
  • createAction() : Flux standard action 지원
  • createSlice() : duck 패턴 지원 
  • createSelector : Reselector 지원 

configStore

store를 생성하고 미들웨어를 추가하는 부가적인 작업들이 아래와 같이 간단하게 변경 되었습니다.

const store = configureStore({
  reducer: rootReducer,
  middleware: [thunk, logger],
})

createSlice

createReducer & createAction 기능을 통합하여 createSlice 라는 것이 생겼습니다.

더 자세히 알고 싶으시다면, ducks패턴 에 대해서 알아 보시면 됩니다.

import { createSlice } from '@reduxjs/toolkit'

const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo(state, action) {
      const { id, text } = action.payload
      state.push({ id, text, completed: false })
    },
    toggleTodo(state, action) {
      const todo = state.find(todo => todo.id === action.payload)
      if (todo) {
        todo.completed = !todo.completed
      }
    }
  }
})

export const { addTodo, toggleTodo } = todosSlice.actions

export default todosSlice.reducer

구문

createSlice는 옵션과 함께 옵션 객체를 인수로 사용합니다.

  • name: 생성 된 조치 유형의 접 두부로 사용되는 문자열
  • initialState : Reducer의 초기 상태 값
  • reducers : 초기 문자열이 키가 되며, 이후 함수는 실행될 내용입니다. (case reducers)

불필요한 'default' 처리

default 처리가 필요 없습니다. createSlice는 현재 상태를 반환하여 주므로 switch문을 사용하던 것과 같이 default를 나열하지 않아도 됩니다.

immutable로 부터 해방

state는 불편 하게 처리 하여야 되기 때문에 수정이 불가능 하였습니다. RSK는 createReducer, createSlice의 API의 내부적 처리를 대신 해주기 때문에 mutable한 state 객체를 주게 됩니다.

createAction

redux-actions 에서 지원하는 API중 createAction를 동일한 이름 을 지원 합니다.

const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'

function increment() {
  return { type: INCREMENT }
}

function decrement() {
  return { type: DECREMENT }
}

function counter(state = 0, action) {
  switch (action.type) {
    case INCREMENT:
      return state + 1
    case DECREMENT:
      return state - 1
    default:
      return state
  }
}

const store = Redux.createStore(counter)

document.getElementById('increment').addEventListener('click', () => {
  store.dispatch(increment())
})

위 구문이 createAction를 사용하면 아래 구문이 됩니다.

const increment = createAction('INCREMENT')
const decrement = createAction('DECREMENT')

function counter(state = 0, action) {
  switch (action.type) {
    case increment.type:
      return state + 1
    case decrement.type:
      return state - 1
    default:
      return state
  }
}

const store = Redux.createStore(counter)

document.getElementById('increment').addEventListener('click', () => {
  store.dispatch(increment())
})

payload

FSA(Flux Standard Actions) 규약은 임의의 이름을 가진 데이터 필드를 직접 가지지 않고, payload라는 이름을 가진 객체에 데이터 필드를 가져야 된다고 제안 합니다.
즉, {type, id, password}가 있다면, id와 password를 payload에 넣어 {type, payload}로 구성 한다는 것입니다.

interface Action<Payload> extends AnyAction {
  type: string
  payload: Payload
  error?: boolean
  meta?: Meta
}

createReducer

action과 reducer를 연결해주는 역할을 하며, redux-actions에서는 handleAction 이라는 API로 지원 되었습니다. 이를 createReducer라는 이름으로 지원 합니다.

import { createAction, createReducer } from 'redux-toolkit'

const increment = createAction('INCREMENT')
const decrement = createAction('DECREMENT')

const counter = createReducer(0, {
  [increment.type]: state => state + 1,
  [decrement.type]: state => state - 1,
})

const store = configureStore({
  reducer: counter,
})

또, createSlice 와 동일하게, immutable 관련 문제를 지원 합니다.

createSelector

Reselector API를 통해 사용하던 selector 기능 입니다. store에 어떤 값에 반복 접근할때 memorization을 통해 성능을 향상 시키게 됩니다. Vue진영에서는 Vuex getter, MobX는 computed로 기본 적으로 제공 되던 기능인듯 합니다.

import { createSelector } from '@reduxjs/toolkit'

const selectTodos = state => state.todos
const selectFilter = state => state.visibilityFilter

const selectVisibleTodos = createSelector(
  [selectTodos, selectFilter],
  (todos, filter) => {
    switch (filter) {
      case VisibilityFilters.SHOW_ALL:
        return todos
      case VisibilityFilters.SHOW_COMPLETED:
        return todos.filter(t => t.completed)
      case VisibilityFilters.SHOW_ACTIVE:
        return todos.filter(t => !t.completed)
      default:
        throw new Error('Unknown filter: ' + filter)
  }
}

prefix가 get보다 select를 권장한다고 합니다.

'React.js' 카테고리의 다른 글

WebStorm 에서 prettier / ESLint 사용하기  (0) 2020.02.15

+ Recent posts