[setting] Next  + Redux
urTweet

[setting] Next + Redux

반응형

Preview

reducer, saga, store, type 설정

store/rootReducer.ts

전체 코드

reducer

여러 reducer 들을 combineReducers로 하나로 묶음

const reducer = combineReducers({
  [FETCH_STATUS]: fetchStatusReducer,
  [USER]: userReducer,
  [POST]: postReducer,
  [HASHTAG]: hashtagReducer,
});

RootState[type]

redux에서 state 타입을 항상 써야 하기 때문에 import 하지 않고 global 영역으로 declare 선언

declare global {
  type RootState = ReturnType<typeof reducer>;
}

rootReducer

next-redux-wrapper로 인해 action.type에서 HYDRATE 타입이 호출되기 때문에 구분자 설정

HYDRATE 타입일 때, 이전 state는 초기화가 됨

const rootReducer = (state: RootState | undefined, action: AnyAction) => {
  switch (action.type) {
    case HYDRATE:
      return action.payload;

    default: {
      return reducer(state, action);
    }
  }
};

hooks/useAppRedux.ts

전체 코드

useAppSelector

reducer에서 반환된 데이터는 모두 RootState 범주 안에서 이루어지기 때문에, RootState 타입을 가진 useSelector hooks를 커스텀

import { TypedUseSelectorHook, useSelector } from 'react-redux';

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

useAppDispatch

dispatch의 type은 anyAction으로 적용 되어, 그냥 일반 useDispatch에서 나온 타입과 동일하기 때문에, 사용자 지정 타입을 제외 했다.

store/rootSaga.ts

전체 코드

여러 saga들이 모듈화로 관리 되기 때문에, 모든 saga들이 action을 watching 해야하기 때문에 전체 관리 rootSaga를 만들어줌

export default function* rootSaga() {
  yield all([fork(userSaga), fork(postSaga), fork(hashtagSaga)]);
}

store/configStore.ts

전체 코드

CSR 설정이었으면, makeStore를 만들 필요 없이 configureStore를 하게되지만, SSR 설정으로 유저가 요청할 때 마다 redux store를 새로(configureStore) 생성하게 되므로 redux store가 여러 개가 될 수 있다.

makeStore로 하나의 스토어를 다루도록 설정하여, front-server에서 redux-store가 여러 개 생성되는것을 방지한다.

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import createSagaMiddleware from 'redux-saga';

import rootReducer from './rootReducer';
import rootSaga from './sagas';

const devMode = process.env.NODE_ENV === 'development';

// Next.js를 사용하게 되면 유저가 요청할 때 마다 
// redux store를 새로(configureStore) 생성하게 되므로 redux store가 여러 개가 될 수 있다.
// makeStore로 하나의 스토어를 다루도록 설정
const makeStore: MakeStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const store = configureStore({
    reducer: rootReducer,
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        // async Saga를 위해 resolve, reject를 반환해서 에러가 생기는 이슈로,
        // serializableCheck 미들웨어를 사용안 함
        serializableCheck: false, 
      }).concat(sagaMiddleware),
    devTools: devMode,
  });
  store.sagaTask = sagaMiddleware.run(rootSaga);
  return store;
};

const wrapper = createWrapper(makeStore, {
  debug: devMode,
});

export default wrapper;

pages/_app.tsx

전체코드

코드 레이아웃 영역이다. header에 들어갈 공통 영역 및, 전체 글로벌 설정 영역을 여기서 다룬다고 생각하면 된다.

여기서 store/configStore.ts에서 생성한 wrapper를 HOC 패턴으로 App을 인자로 전달한다.

이 설정이 없으면, HYDRATE action.type을 못 받게 되므로, SSR을 할 수 없게 된다.

import  wrapper  from  '@modules/store/configStore';

// ...

export default wrapper.withRedux(App);

typings/redux.d.ts

전체 코드

redux는 타입스크립트가 내장되어 있지만, helper 함수 혹은, next-redux-wrapper로 추가 타입을 지정 해야할때, 전역으로 관리되는 type을 하나의 파일에서 지정한다.

Dispatch 타입 해당 이슈

import { ThunkAction } from '@reduxjs/toolkit';
import 'redux';
import { Task } from 'redux-saga';

// Next Redux Toolkit Saga를 사용할때는
// configureStore에서 강제로 sagaTask를 만들어주기 위함
declare module 'redux' {
  export interface Store {
    sagaTask: Task;
  }
  export interface Dispatch<A extends Action = AnyAction> {
    <T extends ThunkAction<any, any, any, any>>(action: T): T extends ThunkAction<infer K, any, any, any> ? K : never;
  }
}

code view

github

 

GitHub - yjkwon07/urTweet: React 라이브러리를 사용하면서 유지보수 및 생산성을 높이기 위해, 폴더, 데

React 라이브러리를 사용하면서 유지보수 및 생산성을 높이기 위해, 폴더, 데이터 구조화 modules를 정의 - GitHub - yjkwon07/urTweet: React 라이브러리를 사용하면서 유지보수 및 생산성을 높이기 위해, 폴

github.com

 

반응형