redux toolkit과 간단한 API 3개를 간단하게 정리해 본다.
기존 Redux의 세가지 문제점을 해결하기 위해 만들어진 패키지
redux 로직을 작성하는 표준방식으로서의 역할을 목적으로 한다.
이는 기존 리덕스의 복잡성을 낮추고 사용성을 높이기 위함이라고 볼 수 있다.
간소화된 구성 옵션과 좋은 기본값을 제공하기 위해 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를 추가해야 하는 경우에 주로 유용하다.
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) => {})
}
)
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++
}
)
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'
* }
* }
**/
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 (리덕스 툴킷)은 정말 천덕꾸러기일까?