Next.js에 리덕스 툴킷 더하기 (with. next-redux-wrapper)

MIMI·2022년 12월 14일
4

https://github.com/seomimi/nextapp/tree/with_redux-tookit
이 글에서는 이 전에 작성했던 https://velog.io/@mimi0905/AWS-Amplify로-next-프로젝트-배포하기SSR 게시글의 코드를 가져와서 추가, 수정합니다.

리덕스 툴킷이란?

리덕스 툴킷 패키지는 리덕스 논리를 작성하는 표준 방법으로, Redux 사용 시 생기는 아래와 같은 불편함을 해결하기 위해 만들어졌습니다.

  • 리덕스 스토어 구성이 너무 복잡함
  • 리덕스의 효율적인 작업 수행을 위해서는 많은 패키지 추가가 필요함
  • 리덕스에는 너무 많은 반복된 코드가 쓰임

리덕스 툴킷을 사용하면 리덕스 코드 개선에 도움을 주기 때문에 기존 애플리케이션 단순화에 기여할 수 있습니다.

모듈 설치하기

@reduxjs/toolkit, react-redux, next-redux-wrapper 의 설치가 필요합니다.

설치한 모듈버전
"next": "^12.2.2",
"next-redux-wrapper": "^8.0.0",
"@reduxjs/toolkit": "^1.9.1"

next-redux-wrapper 를 사용하는 이유

Next.js에서 정적 앱을 만들거나 서버 측 렌더링을 하기 위해서는 서버에 다른 스토어 인스턴스가 필요합니다. 이 서버 스토어는 리덕스 스토어에 접근하여, 서버쪽 데이터와 리덕스의 데이터를 일치 시킬 수 있어야 합니다. 이 접근을 가능하게 해주는 것이 next-redux-wrapper입니다.

next-redux-wrapper 에서 사용할 함수 및 핸들러

  • HYDRATE : 리듀서의 클라이언트 상태를 새로 쓰는 액션 핸들러
  • createWrapper() : 스토어를 앱에 제공하기 위한 wrapper를 생성

리덕스 툴킷에서 사용할 함수

  • createSlice() : 리듀서 함수의 객체, 슬라이스 이름, 초기 상태 값을 받아 해당 액션 생성자와 액션 타입으로 슬라이스 리듀서를 자동으로 생성
  • configureStore() : createStore의 기능, 슬라이스 리듀서를 자동으로 결합하고, 제공하는 모든 Redux 미들웨어를 추가하고, redux-thunk기본적으로 포함하며, Redux DevTools Extension을 사용할 수 있음
  • combineReducers() : 각각 작성 된 리듀서들을 하나로 통합

전체 작업 과정

1. 리듀서 생성

로그인 후 사용자의 아이디(이메일)을 저장하는 슬라이스 리듀서를 생성합니다.

// store/reducer/user.ts
import { createSlice } from '@reduxjs/toolkit';
interface userType {
    email: string | null;
}
const initialState: userType = {
    email: null,
};
export const slice = createSlice({
    name: 'user',
    initialState,
    reducers: {
        login: (state, action) => {
            state.email = action.payload;
        },
    },
});
export const user = slice.name;
export const userReducer = slice.reducer;
export const userAction = slice.actions;

2. 스토어 생성

HYDRATE 상태를 정의하여야, 초기 렌더링 시 서버 측 데이터를 클라이언트에 저장하여 사용할 수 있습니다.

// store/index.ts
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { HYDRATE, createWrapper } from 'next-redux-wrapper';
import { userReducer } from './reducer/user';

const rootReducer = combineReducers({ user: userReducer });

export const makeStore = () => {
    const store = configureStore({
        reducer: (state, action) => {
            switch (action.type) {
                case HYDRATE:
                    return action.payload;
                default:
                    return rootReducer(state, action);
            }
        },
        devTools: process.env.NODE_ENV !== 'production',
    });
    return store;
};

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

export type RootState = ReturnType<typeof rootReducer>;
export default wrapper;

3. 앱에 스토어 제공

이 때, 설치한 next-redux-wrapper 버전에 따라 코드가 약간 다릅니다.

next-redux-wrapper 8버전의 경우,

useWrappedStore()를 사용하여 store를 제공합니다.

// pages/_app.tsx
import wrapper from '../store';
import { Provider } from 'react-redux';
function MyApp({ Component, pageProps }: AppProps) {
    const { store, props } = wrapper.useWrappedStore(pageProps);
    return (
        <Provider store={store}>
            <Component {...props} />
        </Provider>
    );
}
export default MyApp;

next-redux-wrapper 8이전 버전의 경우,

withRedux로 앱을 감싸서 store 제공합니다.
MyApp을 함수형이 아닌 클래스형으로 작성했을 때에도 아래와 같은 방법을 사용합니다.

import wrapper from '../store';
// 함수형
function MyApp({ Component, pageProps }: AppProps) {
    return <Component {...pageProps} />;
}
/*
// 클래스형
class MyApp extends React.Component<AppProps> {
  render() {
    const {Component, pageProps} = this.props;
    return <Component {...pageProps} />;
  }
}
*/
export default wrapper.withRedux(MyApp);

4. 액션 실행

// pages/index.tsx
import type { NextPage, GetServerSideProps } from 'next';
import wrapper, { RootState } from '../store';
import { userAction } from '../store/reducer/user';
import { useSelector, useDispatch } from 'react-redux';

export const getServerSideProps: GetServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
    const { Auth } = withSSRContext(context);
    const user = await Auth.currentUserInfo()
        .then((res: any) => {
            store.dispatch(userAction.login(res.attributes.email || null));
          // 로그인 된 상태일 떄, 스토어에 email을 저장
            return res?.attributes.email || null;
        })
        .catch((error: Error) => {
            console.log(error);
            return null;
        });
    return { props: { } };
});

로그인 안 된 상태일때

로그인 된 상태일때

오류 사항

만약에 리덕스 스토어 생성시 HYDRATE상태를 정의하지 않으면
서버 측 스토어에는 아래와 같이 데이터가 저장되어 있지만,

클라이언트 측 스토어에는 데이터가 저장되어 있지 않습니다.

profile
Web Developer

0개의 댓글