nextjs - redux toolkit

npx create-next-app@latest --typescript
npm i @reduxjs/toolkit
npm i react-redux
npm i next-redux-wrapper
npm i redux-logger --save-dev # 필요한 경우에 설치

├── README.md
├── next.config.js
├── node_modules
├── package-lock.json
├── package.json
├── pages
├── public
├── store
│├── modules
││ ├── counter.js (리듀서 모듈)
││ └── index.js (리듀서 모듈 통합) 
│└── index.js (store 생성 && wrapper 생성) 
└── styles

store/modules/conter.js

import { createSlice } from '@reduxjs/toolkit';

const initialState = { value: 0 }; // 초기 상태 정의

const counterSlice = createSlice({
	name: 'counter',
    initialState,
    reducers: {
      increment: state => { state.value += 1 },
      decrement: state => { state.value -= 1 },
    }, 
});

export const { increment, decrement } = counterSlice.actions; // 액션 생성함수
export default counterSlice.reducer; // 리듀서

다른 컴포넌트/모듈 내에서 사용하기 위해 정의한 리듀서와 액션 생성함수를 export한다.

● createSlice: action과 reducer를 한 번에 정의한다.
● createAction + createReducer = createSlice
● 비동기적인 리듀서 함수를 정의하고자 할 땐 객체의 프로퍼티로 extraReducers객체를 추가한다.

store/modules/index.js

import { combineReducers } from "@reduxjs/toolkit";
import { HYDRATE } from "next-redux-wrapper";
import counter from './counter';

const reducer = (state, action) => {
	if (action.type === HYDRATE) {
    	return { ...state, ...action.payload };
    }
    
    return combineReducers({
    	counter,
        // 여기에 추가
    })(state, action); 
};

export default reducer;

modules 내에서 정의한 모듈들을 합쳐주는 역할을 한다.

if (action.type === HYDRATE): SSR작업 수행 시 HYDRATE라는 액션을 통해서 
서버의 스토어와 클라이언트의 스토어를 합쳐주는 작업을 수행한다.
combineReducers: 함수명 그대로 정의한 리듀서 모듈들을 결합하는 역할을 한다.
리듀서 모듈(slice)을 추가할 때마다 combineReducers함수의 인자로 전달되는 객체 내부에 추가해준다.

store/index.js

import { configureStore } from '@reduxjs/toolkit';
import { createWrapper } from "next-redux-wrapper";
import logger from 'redux-logger';
import reducer from './modules';
const makeStore = (context) => configureStore({
  reducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
  devTools: process.env.NODE_ENV !== 'production',
});

export const wrapper = createWrapper(makeStore, {
	debug: process.env.NODE_ENV !== 'production',
});

스토어를 생성하는 함수를 정의하고, 이를 인자로 wrapper HOC를 만든다.

configureStore: store를 생성한다.
redux-toolkit은 devTools 등의 미들웨어들을 기본적으로 제공한다.
여기에 사용하고 싶은 미들웨어가 있다면 추가로 정의해준다.
위의 코드에선 logger를 추가해줬다.
createWrapper: wrapper를 생성한다.
지금까지 리듀서 모듈 만들기 -> 리듀서 모듈 합치기 -> 스토어 생성의 과정을 거쳤다.
이제 최종적으로 wrapper에 스토어를 바인딩해주면 된다.

pages/_app.js

import '../styles/globals.css';
import { wrapper } from "../store";

function MyApp({ Component, pageProps }) {
	return <Component {...pageProps} /> 
}

export default wrapper.withRedux(MyApp);

wrapper의 withRedux HOC로 App컴포넌트를 감싸준다.

이제 각 페이지에서 getStaticProps, getServerSideProps 등의
함수 내에서 스토어 접근이 가능해진다.

작동을 확인하기 위해 다음과 같이 pages 디렉토리 내에
counter.js(counter.jsx)를 작성해보자.

pages/counter.js

import { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as counterActions from '../store/modules/counter';

export default function Test() {
	const dispatch = useDispatch();
    const value = useSelector(({ counter }) => counter.value);
    const plus = useCallback(({ value }) => {
    	dispatch(counterActions.increment({ value }));
    }, [dispatch]);
    
    const minus = useCallback(({ value }) => { 
    	dispatch(counterActions.decrement({ value }));
    }, [dispatch]);
    
    return (
    	<div>
        	<h1>Counter</h1>
            <button onClick={() => minus({ value })}>-</button>
            <span>{value}</span>
            <button onClick={() => plus({ value })}>+</button>
        </div>
   ); 
}

좋은 웹페이지 즐겨찾기