redux toolkit-대표적인 API 3개

Rosevillage·2023년 2월 27일
1

redux toolkit과 간단한 API 3개를 간단하게 정리해 본다.

Redux toolkit

기존 Redux의 세가지 문제점을 해결하기 위해 만들어진 패키지

  1. store의 과한 복잡성
  2. 패키지 추가 설치의 찾은 빈도수
  3. 너무 많은 상용구 코드

redux 로직을 작성하는 표준방식으로서의 역할을 목적으로 한다.
이는 기존 리덕스의 복잡성을 낮추고 사용성을 높이기 위함이라고 볼 수 있다.

대표적인 API

configureStore

간소화된 구성 옵션과 좋은 기본값을 제공하기 위해 createStore를 래핑한다.

slice reducer를 자동으로 결합하고, 제공하는 모든 Redux 미들웨어를 추가한다. 기본적으로 redux-thunk를 포함하고, Redux DevTools Extension도 사용할 수 있다.

//기본
import { configureStore } from '@reduxjs/toolkit'

import rootReducer from './reducers'

const store = configureStore({ reducer: rootReducer })

//전체 옵션
import logger from 'redux-logger'

import { batchedSubscribe } from 'redux-batched-subscribe'

import todosReducer from './todos/todosReducer'
import visibilityReducer from './visibility/visibilityReducer'

const reducer = {
  todos: todosReducer,
  visibility: visibilityReducer,
}

const preloadedState = {
  todos: [
    {
      text: 'Eat food',
      completed: true,
    },
    {
      text: 'Exercise',
      completed: false,
    },
  ],
  visibilityFilter: 'SHOW_COMPLETED',
}

const debounceNotify = _.debounce(notify => notify());

const store = configureStore({
  reducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
  devTools: process.env.NODE_ENV !== 'production',
  preloadedState,
  enhancers: [batchedSubscribe(debounceNotify)],
})
  • reducer
    단일 함수일 경우 스토어의 root reducer로 직접 사용된다.
    slice의 객체인 경우 자동으로 combineReducers에 전달해 루트 리듀서를 생성한다.

  • middeware
    옵션이 제공되면 추가하려는 모든 미들웨어 기능이 포함되야 하며, 자동으로 applyMiddleware를 통해 configureStore에 전달된다.

    아무것도 제공되지 않으면 자동으로 getDefaultMiddleware를 호출하고 사용한다.

  • devTools
    boolean값을 통해 Redux DevTools 브라우저 확장에 대한 지원을 활성화 여부를 결정한다.

    기본값은 true

  • preloadedState
    Redux createStore 함수에 전달할 선택적 초기 상태 값

  • enhancers
    배열로 정의하면 compose함수에 전달되고 결합된 enhancer는 createStore에 전달된다.

    콜백 함수로 정의된 경우 DevTools Extension없이 기존 enhancer 배열로 호출되며 새로운 enhancer 배열을 반환해야 한다. 이는 redux-first-router 또는 redux-offline과 같이 applyMiddleware 앞에 스토어 enhancer를 추가해야 하는 경우에 주로 유용하다.

createReducer

switch 문을 작성하는 대신 case reducer 함수(특정 작업 유형을 처리하기 위한 함수, 스위치의 단일 case 문과 동일)에 action.type의 조회 테이블을 제공할 수 있다.

자동으로 immer 라이브러리를 사용하여 state.todos[3].completed = true와 같이 간단하게 변경 불가능한 업데이트를 작성할 수 있다.

builder callback표기법, map object 표기법중 선택해 정의할 수 있으며, 보통 builder callback 표기법이 선호된다.

Builder Callback

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

function isActionWithNumberPayload(action) {
  return typeof action.payload === 'number'
}

const reducer = createReducer(
  {
    counter: 0,
    sumOfNumberPayloads: 0,
    unhandledActions: 0,
  },
  (builder) => {
    builder
      .addCase(increment, (state, action) => {
       // action is inferred correctly here
        state.counter += action.payload
      })
      .addCase(decrement, (state, action) => {
        state.counter -= action.payload
      })
      .addMatcher(isActionWithNumberPayload, (state, action) => {})
      .addDefaultCase((state, action) => {})
 }
)
  • builder.addCase(actionCreator, reducer)
    action type과 맵핑되는 case reducer를 추가해 실행한다.
  • builder.addMatcher(matcher, reducer)
    새로 들어오는 action에 대해서 주어진 패턴과 일치하는지 확인하고 리듀서를 실행한다.
  • builder.addDefaultCase(reducer)
    addCase나 addMatcher가 실행되지 않은 경우 default case reducer가 실행된다.

Map Object

const isStringPayloadAction = (action) => typeof action.payload === 'string'

const lengthOfAllStringsReducer = createReducer(
  // initial state
  { strLen: 0, nonStringActions: 0 },
  // normal reducers(actionsMap)
  {
    /*...*/
  },
  //  array of matcher reducers(actionMatchers)
  [
    {
      matcher: isStringPayloadAction,
      reducer(state, action) {
        state.strLen += action.payload.length
      },
    },
  ],
  // default reducer
  (state) => {
    state.nonStringActions++
  }
)
  • initialState
    초기값
  • actionsMap
    action type이 case reducer에 맵핑되어 있는 객체
  • actionMatchers
    {matcher, reducer} 형태의 일치자 정의 배열로 case reducer가 일치하는지 여부에 관계없이 모든 일치하는 reducer가 순서대로 실행
  • defaultCaseReducer
    case reducer 및 matcher reducer가 실행되지 않은 경우 실행되는 default case reducer

createAction

unction createAction(type, prepareAction)

action type과 action creator 함수를 한번에 정의해 생성시켜주는 함수로 기존에는 별도로 선언하던 방식을 간결하게 바꿔준다.

// 기존 방식
const INCREMENT = 'counter/increment'

function increment(amount) {
  return {
    type: INCREMENT,
    payload: amount,
  }
}

const action = increment(3)
// { type: 'counter/increment', payload: 3 }

// createActiont
const increment = createAction('counter/increment')

let action = increment()
// { type: 'counter/increment' }

action = increment(3)
// returns { type: 'counter/increment', payload: 3 }

payload에 여러 매개 변수를 추가하고자 한다면 prepareActiond 콜백함수를 추가해 사용할 수 있다.

const addTodo = createAction('todos/add', function prepare(text) {
  return {
    payload: {
      text,
      id: nanoid(),
      createdAt: new Date().toISOString(),
    },
  }
})

console.log(addTodo('Write more docs'))
/**
 * {
 *   type: 'todos/add',
 *   payload: {
 *     text: 'Write more docs',
 *     id: '4AJvwMSWEHCchcWYga3dj',
 *     createdAt: '2019-10-03T07:53:36.581Z'
 *   }
 * }
 **/

createSlice

initialState와 name을 받아 reducer와 상태에 해당하는 action creator와 action type을 자동으로 생성하는 함수

내부적으로 createAction, createReducer를 사용하므로 Immer를 통해 불변 업데이트를 작성할 수 있다.

const initialState = { value: 0 }

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment(state) {
      state.value++
    },
    decrement(state) {
      state.value--
    },
    incrementByAmount(state, action) {
      state.value += action.payload
    },
  },
  extraReducers: (builder) => {
  	//builder
    //  .addCase(incrementBy, (state, action) => {
    //  })
    // ....
  }
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
  • initialState
    상태 초기값으로 지연 초기화 함수가 들어올 수도 있다.

  • name
    slice의 이름으로 action type의 접두사로 사용된다.

  • reducers
    case reducer를 포함하는 객체로
    객체의 키는 action type을 생성하는 데 사용되며, 접두사를 포함한 동일한 문자열을 가지는 action이 오면 case reducer가 실행된다

  • extraReducers
    createSlice가 생성한 유형 외에 다른 작업 유형에 응답하는reducers

    extraReducers로 지정된 케이스 리듀서는 "외부" 액션을 참조하기 위한 것이기 때문에 slice.actions에서 생성된 액션을 가지지 않는다.

    reducers와 마찬가지로 이러한 case reducer도 createReducer로 전달되며 상태를 안전하게 "변경"할 수 있다.

    reducers와 extraReducers의 두 필드가 동일한 action type으로 끝나는 경우 reducers가 우선된다.


middle ware에 대해서는 따로 정리할 예정이다.


참고 사이트

Redux Toolkit-Getting Started with Redux Toolkit

Redux Toolkit-API Reference

화해 블로그-김규식-Redux Toolkit (리덕스 툴킷)은 정말 천덕꾸러기일까?

0개의 댓글