redux-toolkit은 redux에서 자주쓰는 기능을 모아둔 라이브러리이다. 매우 유용해서 요즘 많이 쓰이고 있음 redux-toolkit에는 thunk, immer, createAction도 모두 내장되어 있다. 따라서 코드량이 확 줄어든다! 🤩
요즘에는 redux-saga → thunk + redux-toolkit을 조합하는 것으로 많이 전환하는 추세인 것 같다. saga에서 제공하는 다양한 이펙트들이 있으나, 주로 사용하는 기능은 takeLatest, takeEvery 정도이고, debounce나 throttle의 경우 lodash와 thunk와의 조합으로 충분히 구현해낼 수 있으므로 굳이 saga를 적용하지 않게되는 것이다. 또 제너레이터 문법에 익숙하지 않은 동료들과의 협업하는 관점에서도 고민되는 부분이 있는 것 같다.
위와 같은 이유로 지금까지 만들어왔던 react-redux 프로젝트를 Redux-toolkit으로 바꿔서 적용해보자.
먼저 툴킷을 이용하면 store 부터 굉장히 가벼워진다. 위에 적었다시피 toolkit이 이미 thunk나 immer들을 내장함수로 가지고 있기 때문이다. 따라서 기존 store의 코드에 redux-toolkit을 적용하면 아래와 같다.
const { configureStore, getDefaultMiddleware } = require("@reduxjs/toolkit");
const reducer = require("./reducers");
const firstMiddleware = (store) => (dispatch) => (action) => {
console.log("로깅:", action);
dispatch(action);
};
const store = configureStore({
reducer,
middleware: [firstMiddleware, ...getDefaultMiddleware], // getDefaultMiddleware 기본 미들웨어 추가
devTools: process.env.NODE_ENV !== "production",
// enhancer
// preloadedState, // SSR용 설정
});
module.exports = store;
기존의 action도 action과 actionCreator를 분리하여 생성하던 이전 과정과는 달리 createAsyncThunk
를 사용해서 간단하게 구현할 수 있다. redux-toolkit에서는 action에 대한 정의를 비동기 액션, 혹은 외부적 액션에 대한 것만 적어주면된다. (동기액션, 곧 컴포넌트 내부의 액션은 리듀서에서 정의하면 자동 생성된다)
actions/user.js
const { createAsyncThunk } = require("@reduxjs/toolkit");
const logIn = createAsyncThunk("user/logIn", async (data, thunkAPI) => {
return await delay(500, { userId: 1, nickname: "vicky" });
});
module.exports = { logIn }
actions/post.js
const { createAsyncThunk } = require("@reduxjs/toolkit");
const addPost = createAsyncThunk("post/add", async (data, thunkAPI) => {
return await delay(500, { title: "새 게시글", content: "내용이 들어간다" });
});
module.exports = { addPost };
createAsyncThunk 메서드 안에 첫번째 인자로 user/login
, post/add
등의 경로를 적어주는데 위와 같이 적어주면, 리듀서에서 정의한 pending - fulfilled - rejected라는 메서드가 뒤에 붙어 redux-devtools에서 데이터 변경 트래킹이 가능해진다.
reducers/index.js
reducer도 굉장히 가벼워지는데, userReducer, postReducer 대신 userSlice, postSlice를 사용한다.
const { combineReducers } = require("redux");
const userSlice = require("./user");
const postSlice = require("./post");
module.exports = combineReducers({
user: userSlice.reducer,
posts: postSlice.reducer,
});
먼저 user 리듀서를 구현해보면 아래와 같다.
reducers/user.js
const { createSlice } = require("@reduxjs/toolkit");
const { logIn } = require("../actions/user");
const initialState = {
isLoggingIn: false,
data: null,
};
const userSlice = createSlice({
name: "user",
initialState,
reducers: { // 동기적 액션, 내부적 액션, userSlice.actions.logOut()으로 액션이 자동 생성됨
logOut(state, action) {
state.data = null;
},
},
extraReducers: { // 비동기 액션, 외부적 액션 (pending, fulfilled, rejected로 사용)
// user/logIn/pending
[logIn.pending](state, action) {
state.isLoggingIn = true;
},
// user/logIn/fulfilled
[logIn.fulfilled](state, action) {
state.data = action.payload;
state.isLoggingIn = false;
},
// user/login/rejected
[logIn.rejected](state, action) {
state.data = null;
state.isLoggingIn = false;
},
},
});
module.exports = userSlice;