리덕스를 편리하게 사용할 수 있게 도와주는 🎁종합선물세트 '리덕스 툴킷'에 대해 알아보자.
리덕스를 사용하다보면 액션타입,액션생성함수, 리듀서 등 반복되는 코드,즉 보일러플레이트 코드때문에 피로감을 느낄 수 있다.
이때 리덕스 툴킷을 사용하면 코드를 좀 더 간결하게 만들어 줄 수 있다.
리덕스 툴킷은 리덕스의 종합선물세트로 redux thunk, immer, createaction 등이 내장되어 있다.
toolkit을 사용하기 전에는 createStore()를 호출하고 root reducer함수를 전달하여 redux store를 사용했다. Toolkit의 configureStore는 createStore를 활용한API로 createStore를 래핑하여 만들어진 함수이기때문에 기본적으로 createStore와 동일한 기능을 제공한다.(store 생성)
(no toolkit)
import { createStore } from 'redux';
import rootReducer from './module/rootReducer';
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = createStore(rootReducer, devTools);
(with toolkit)
createStore -> configureStore.
configureStore함수는 여러 개의 인자 대신 지정된 하나의 object를 인자로 받으므로, reducer함수를 reducer라는 이름으로 전달해야 한다. 리듀서만 전달하면, 기본적으로 redux devtool와 middleware가 포함되어 있다.
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({ reducer: rootReducer });
사용하고싶은 미들웨어를 지정하려면 middleware의 값을 아래와 같은 배열로 전달하면 된다.기본으로 제공하는 미들웨어에 logger를 추가하려면 getDefaultMiddleware를 사용하면 된다.
const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger],
});
//getDefaultMiddleware 사용하는 경우
onst store = configureStore({
reducer: rootReducer,
middleware: [...getDefaultMiddleware(), logger],
});
createAction은 action을 간결하게 만들어 준다.
(no toolkit)
const INCREMENT = "counter/increment"; // 액션
const DECREMENT = "DECREMENT";
// 액션 크리에이터 함수
const increment = () => {
return {
type: INCREMENT,
};
}
const decrement = () => {
return {
type: DECREMENT,
};
}
// or 인자가 있는 경우
function increment = (amount) => {
return {
type: INCREMENT,
payload: amount,
};
}
const action = increment(2);
// { type: 'INCREMENT', payload: 2 }
(with toolkit)
createAction으로 액션을 전달하기만 하면 액션과 액션 크리에이터 함수가 만들어진다.
만약 이 생성함수를 호출할 때 prameter를 추가로 넣어준다면 알아서 payload에 값이 들어가게 된다.
import { createAction } from "@reduxjs/toolkit";
const INCREMENT = "INCREAMENT"; // 액션
const DECREMENT = "DECREMENT";
export const increment = createAction(INCREMENT);
export const decrement = createAction(DECREMENT);
// 파라미터가 있는 경우, 확실히 명시해주기위해 코드상으로 명시해줄 수도 있다.
export const increment = createAction(INCREMENT, (amount) => amount);
increment(); { type: 'INCREMENT' }
increment(2); { type: 'INCREMENT', payload:2 }
기존에 reducer를 사용하기 위해서 switch 조건문으로 액션타입을 구분해 수행해야 했지만, toolkit을 사용하면 switch와 default를 사용하지 않고, 가독성을 향상시킬 수 있다.
(no toolkit)
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
(with toolkit)
// 액션타입 집어넣는 경우 ok
const counterReducer = createReducer(0, {
[INCREMENT]: state => state + 1,
[DECREMENT]: state => state - 1
})
//or 액션생성함수 집어넣는 경우 ok
const counterReducer = createReducer(0, {
[increment]: state => state + 1,
[decrement]: state => state - 1
})
=> 액션생성함수를 바로 집어넣을 수 있는 이유는 createAction함수가 toString메소드를 오버라이드했기 때문( ex)incerement 함수 => return INCREMENT)
리듀서, 액션타입, 액션 생섬함수, 초기상태를 하나의 함수로 편하게 선언할 수 있게 해준다. 쉽게말해 action과 reducer를 다 가지고 있는 함수.
const counterSlice = createSlice({
name: 'counter', //액션 경로를 잡아줄 해당 이름
initialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1
}
})
const store = configureStore({
reducer: counterSlice.reducer
})
기존에는 액션생섬함수와 액션타입을 선언해 사용했지만, createSlice에서는 Action을 선언하고, 해당 action이 dispatch되면 state를 가지고 action을 처리한다.reducers 안에 모든 기능이 합쳐져 있는 것이다.
기존에 불변성을 유지하기 위해 전개연산자나 concat의 메소드를 사용했어야 했는데 Toolkit에서는 자동으로 불변성을 관리해주는 유틸을 가지고 있다.
immer 라이브러리를 내장하고 있기때문에 더이상 리듀서에서 새로운 state객체를 만들어 리턴하지않고, state를 직접 변경해도 된다.