Redux-thunk나 Redux-saga로 서버, 글로벌 상태를 관리하며 개발을 해오다,, 프로젝트 규모가 커질수록 점점 막대해지는 서버 상태로 방대해지는 반복 코드로 인하여 서버와 글로벌의 상태 분리가 필요하다고 느꼈고, 서버 상태관리를 위하여 react-query를 도입했다!
그리하여, 글로벌 상태 관리는 더이상 thunk나 saga가 적합하지 않고, 게다가 redux를 사용하면서 느꼈던 불편함들이 많았기 때문에..
이러한 문제점을 보완하여 Redux에서는 Redux Toolkit을 만들어냈다!
이번 포스팅은 Redux Toolkit 공식문서와 Vercel에서 오픈한 Next.js + Typescript + Redux-Toolkit 소스코드를 참고하여 작성하였다.
$ npx create-next-app 프로젝트명 --typescript
$ npm i @reduxjs/toolkit
$ npm i react-redux
$ npm i next-redux-wrapper
$ npm i @types/react-redux
$ npm i redux-logger # 필요한 경우에 설치
import { createSlice } from '@reduxjs/toolkit';
import type { AppState, AppThunk } from './store';
export interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
// action + reducer 정의
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
},
});
// useSelector에 넣을 함수 내보내기
export const selectCounter = (state: AppState) => state.counter;
// actions 내보내기
export const { increment, decrement } = counterSlice.actions;
// reducer 내보내기
export default counterSlice.reducer;
import { combineReducers } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import counterReducer from './counterSlice';
const reducer = (state, action) => {
// SSR 작업 수행 시 필요한 코드
if (action.type === HYDRATE) {
return {
...state,
...action.payload,
};
}
return combineReducers({
counter: counterReducer,
})(state, action);
};
export default reducer;
if(action.type === HYDRATE)
는 SSR 작업 수행 시 HYDRATE라는 액션을 통해서 서버의 스토어와 클라이언트의 스토어를 합쳐주는 작업을 수행한다.Next.js
는 처음 렌더링 시 SSR
을 하게 된다. 따라서 store
를 호출할 때마다 redux store
를 새로 생성하게 된다. 이 때 생성하는 redux store
와 이후 CSR
시 생성하는 redux store
가 다르기 때문에 이 둘을 합쳐주는 로직이 필요하다.HYDRATE
라는 액션을 통해 client에 합쳐주는 작업을 한다. action.payload
에는 서버에서 생성한 store
의 상태가 담겨있어 이 둘을 합쳐 새로운 client의 redux store
의 상태를 만들게 된다.import { configureStore } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import logger from 'redux-logger';
import reducer from './index';
const isDev = process.env.NODE_ENV === 'development';
const makeStore = () =>
configureStore({
reducer,
middleware: getDefaultMiddleware =>
isDev ? getDefaultMiddleware().concat(logger) : getDefaultMiddleware(),
devTools: isDev,
});
export const wrapper = createWrapper(makeStore, {
debug: isDev,
});
const store = makeStore();
// type 정의
export type AppState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
import type { AppProps } from 'next/app';
import { wrapper } from '../modules/store';
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default wrapper.withRedux(MyApp);
혹시 내용에 오류가 있다면 피드백 부탁드립니다. 🙇🏻♀️
[참고]