[Next.js] next-redux-wrapper, redux-toolkit 사용하기

hyejinJo·2023년 3월 6일
1

React

목록 보기
2/9
post-thumbnail

redux-toolkit: 리덕스 툴킷(redux toolkit) 은 리덕스를 불편한 점을 보완하기위해 나온 개발 도구이다. 리덕스는 복잡한 저장소 구성, 수많은 라이브러리에 대한 의존성, 그리고 하나의 작업마다 많아지는 코드양 등의 문제점이 있었다. 하지만 리덕스 툴킷은 위의 모든 기능을 내장하고 있다.

next-redux-wrapper: next-redux-wrapper 라이브러리가 필요한 이유를 알아보자. 리액트 앱에는 리덕스 스토어가 하나이다. 하지만 Next 를 사용하는 순간 스토어는 여러 개가 될 수 있는데, Next 는 유저가 요청 할 때마다 리덕스 스토어를 새로 생성한다.
또한 Next 에서 제공하는 서버사이드 환경에서 리덕스 스토어에 접근하려면 해당 라이브러리가 없이는 불가능하다.
만약에 순수 next.js만 사용하고 next-redux-wrapper를 사용하지 않는다면 우리는 getInitialProps등에서 리덕스 스토어에 접근할 수 없기 때문에 그런 경우엔 axios나 fetch등의 api 라이브러리를 사용해야한다.

리덕스보다 더 편리한 용도로 나온 redux-toolkit 과, next 프로젝트 환경에서 리덕스를 사용할 수 있는 next-redux-wrapper 를 사용하는 방법을 알아보자.
(리덕스를 붙이려면 많은 과정이 필요하며, next 역시 마찬가지로 복잡하다. 이를 간편하게 위해 next 에서 리덕스를 쓰기 편하도록 해주는 next-redux-wrapper 같은 라이브러리를 사용해준다)


폴더 구조:

모듈 설치

npm i react-redux next-redux-wrapper @reduxjs/toolkit

npm i redux-logger --save-dev


파일 작성

1. 리듀서 모듈 만들기

// store > modules > user

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

const initialState = {
  name: 'hyejin',
  isLoggedIn: false,
}

const userSlice = createSlice({
  name : 'user',
  initialState,
  reducers: {
    changeNickname: (state, action) => { state.name = action.payload }
  }
})

export const { changeNickname } = userSlice.actions; // 액션 생성 함수
export default userSlice.reducer; // 리듀성

2. 리듀서 모듈 합치기

// store > modules > index

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

import { combineReducers } from "redux";
import { HYDRATE } from "next-redux-wrapper";

import user from './user';

const reducer = (state, action) => {
  if(action.type === HYDRATE) { // SSR 작업 수행 시 HYDRATE 라는 액션을 통해서 서버의 스토어와 클라이언트의 스토어를 합쳐주는 작업을 수행
    return {
      ...state,
      ...action.payload
    }
  }
  return combineReducers({ // 정의한 리듀서 모듈들을 결합
    user,
    // 리듀서 모듈(slice)을 추가할 때마다 combineReducers 함수의 인자로 전달되는 객체 내부에 추가해줘야함
  })(state, action);
}

export default reducer;
  • 서버 측(next.js)에서 생성한 redux 스토어와 클라이언트에서 생성한 redux 스토어는 다르기 때문에 이 둘을 합쳐야 한다.
  • HYDRATE 라는 액션을 통해 서버에서 생성한 스토어의 상태를 클라이언트 스토어와 합칠 수 있다.

3. 스토어 생성

앞서 말했듯 next-redux-wrapper 는 유저가 페이지를 요청할때마다 리덕스 스토어를 생성해야 하기 때문에 makeStore 함수를 정의해서 넘겨준다.

// store > index.js

import { configureStore } from "@reduxjs/toolkit";
import { createWrapper } from "next-redux-wrapper";
import logger from "redux-logger";
// redux-logger:
// log 에 색을 입혀주거나, 리덕스 동작에 대한 것을 자세하고 편하게 log 에서 확인할 수 있도록 만들어진 리덕스 미들웨어

import reducer from "./modules";

const makeStore = (context) => configureStore({
  // configureStore: store 를 생성
  reducer,// 리듀서 모듈들이 합쳐진 루트 리듀서 
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
  // redux-toolkit 은 devTools 등의 미들웨어들을 기본적으로 제공 (사용하고 싶은 미들웨어가 있다면 추가로 정의 ex.logger)
  devTools: process.env.NODE_ENV !== 'production',
});

const wrapper = createWrapper(makeStore, {
  // createWrapper: wrapper 생성, wrapper 에 store 바인딩
  debug: process.env.NODE_ENV !== 'production',
})

export default wrapper;

4. _app.js ⇒ HOC 로 컴포넌트 감싸기

wrapperwithRedux HOC로 App컴포넌트를 감싸준다. 그럼 이제 각 페이지의 getInitialProps, getServerSideProps, getStaticProps 등에서 리덕스 스토어에 접근이 가능해진다.

// pages > _app.js

import React from 'react';
import PropTypes from "prop-types";
import Head from "next/head";

import wrapper from "../store";

const Buzzy = ({ Component, pageProps }) => {
  return (
    <>
      <Head>
        <meta charSet="utf-8" />
        <title>Buzzy</title>
      </Head>
      <Component {...pageProps} />
    </>
  );
};

Buzzy.propTypes = {
  Component: PropTypes.elementType.isRequired,
}

export default wrapper.withRedux(Buzzy); // withRedux HOC 로 컴포넌트 감싸기
// -> 이제 각 페이지에서 getStaticProps, getServerSideProps 등 함수 내에서 스토어의 접근이 가능해짐

5. 페이지 컴포넌트 추가 후 테스트해보기

// pages > test

import React, {useCallback} from 'react';
import { useDispatch, useSelector } from "react-redux";
import * as userActions from "../store/modules/user"

const Test = () => {
  const dispatch = useDispatch();
  const name = useSelector(({user}) => user.name)
  // useSelector: 스토어의 상태값을 반환 (connect 함수를 이용하지 않고 리덕스의 state 를 조회할 수 있다.)

  const changeNickname = useCallback(() => {
    dispatch(userActions.changeNickname('heeseong'))
  }, [dispatch])

  return (
    <>
      <h1>test</h1>
      <span>{name}</span>
      <button onClick={() => changeNickname()}>이름 변경</button>
    </>
  );
};

export default Test;

결과

버튼 클릭 전:

버튼 클릭 후:


참고:

https://simsimjae.medium.com/next-redux-wrapper%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0-5d0176209d14

https://velog.io/@jjhstoday/Redux-Redux-Toolkit-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0

profile
FE Developer 💡

1개의 댓글

comment-user-thumbnail
2023년 6월 2일

감사합니다 도움이 많이 됐어요 !

답글 달기