React

[Next.js] create-next-app 환경에서 next.js, typescript, redux 세팅하기

JayLee 2021. 10. 15. 15:29
반응형

 먼저, create-next-app 명령어를 이용해서 기본 프로젝트를 생성합니다. --typescript 명령어를 이용하여 typescript 프로젝트로 설정했습니다.

yarn create next-app --typescript nextjs-typescript-redux

 

그 다음은 필요한 패키지들을 설치합니다.

yarn add redux redux-thunk next-redux-wrapper react-redux @type/react-redux

 

패키지 설치가 완료되었으면, 프로젝트 루트(root)에 redux라는 이름의 폴더를 생성합니다. 그리고 그 내부에 store.ts, types.ts 파일과  actions, reducers라는 이름의 폴더를 생성합니다. 아래 그림과 같이 생성되면 됩니다.

 

types.ts 파일부터 내용을 채워봅니다. 사용자의 이름을 수정하는 타입을 하나 만들었습니다.

export const SET_NAME = "SET_NAME";

 

그 후에 reducers 폴더에 main.ts라는 이름의 파일을 생성합니다. 아래와 같이 코드를 작성합니다. main.ts 파일을 통해 하나의 reducer를 생성하고, 해당 reducer에서 샘플 앱의 mian state를 관리합니다. 초기 name state는 'guest'라는 문자열로 초기화 되고, 이후 SET_NAME action이 실행되면 받아온 value(action.payload) 값으로 변경됩니다.

import { AnyAction } from "redux";
import * as t from "../types";

const main = (
  state = {
    name: "guest",
  },
  action: AnyAction
) => {
  switch (action.type) {
    case t.SET_NAME:
      return {
        ...state,
        name: action.payload,
      };
    default:
      return { ...state };
  }
};

export default main;

 

 reducers 폴더에 rootReducer.ts 파일을 추가합니다. 해당 파일은 reducer들을 하나로 결합해주는 역할을 합니다. 여러개의 reducer를 사용한다면 굉장히 유용한 기능입니다. 내용은 아래와 같이 작성합니다.

import { combineReducers } from "redux";
import main from "./main";

const rootReducer = combineReducers({
  main: main,
});

export type ReducerType = ReturnType<typeof rootReducer>;
export default rootReducer;

 

그 다음으로 store.ts 파일을 작성해봅시다. 이 파일에서는 redux와 next-redux-wrapper 패키지를 이용하여, 우리가 사용할 redux store를 생성합니다. 그리고 redux-thunk 미들웨어를 추가합니다. redux-thunk를 사용하면 액션 객체가 아닌 함수를 디스패치할 수 있습니다.

import { createStore, applyMiddleware, compose } from "redux"
import thunk from "redux-thunk"
import { createWrapper } from "next-redux-wrapper"
import rootReducer from "./reducers/rootReducer"

const middleware = [thunk]

const makeStore: MakeStore<Store<ReducerType, AnyAction>> = () => createStore(rootReducer, compose(applyMiddleware(...middleware)))

export const wrapper = createWrapper(makeStore)

 

이제 action 파일을 만들어봅니다. actions 폴더에 main.ts 파일을 생성합니다. 이 액션을 통해 새로운 이름으로 변경할 수 있습니다. 아래와 같이 작성합니다.

import * as t from "../types";

export const setInfo = (name: string) => (dispatch: any) => {
  dispatch({
    type: t.SET_NAME,
    payload: name,
  });
};

 

이제 _app.tsx 파일을 수정해봅니다. store에서 wrapper을 가져와 MyApp을 감싸주면 됩니다. 이렇게 하면 store가 생성되고 props를 통해 컴포넌트에 store가 전달 됩니다.

import "../styles/globals.css";
import type { AppProps } from "next/app";
import { wrapper } from "../redux/store";

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

export default wrapper.withRedux(MyApp);

 

 index.js 파일을 수정해봅니다. useState를 이용하여 name 값을 저장하고 수정할 수 있도록 했습니다. 그리고 mapDispatchToProps를 이용하여 setInfo Action 함수와 연결했고, mapStateToProps를 이용하여 state와 연결했습니다. 코드는 아래와 같습니다.

import { useState } from "react";
import { connect } from "react-redux";
import { setInfo } from "../redux/actions/main";
import styles from "../styles/Home.module.css";

function Home(props) {
  const { name, setInfo } = props;
  const [newName, setName] = useState("");

  return (
    <div className={styles.container}>
      <p>Enter a Name {name}:</p>
      <input
        type="text"
        value={newName}
        onChange={(e) => setName(e.target.value)}
      ></input>
      <button onClick={() => setInfo(newName)}>Submit</button>
    </div>
  );
}

const mapStateToProps = (state) => {
  return { name: state.main.name };
};

const mapDispatchToProps = {
  setInfo,
};

export default connect(mapStateToProps, mapDispatchToProps)(Home);

 

 마지막으로 Redux Dev Tool과 연결할 수 있도록 store.ts 코드를 아래와 같이 수정합니다.

import { createStore, applyMiddleware, compose, Store, AnyAction } from "redux";
import thunk from "redux-thunk";
import { createWrapper, MakeStore } from "next-redux-wrapper";
import rootReducer, { ReducerType } from "./reducers/rootReducer";

declare global {
  interface Window {
    __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
  }
}

const middleware = [thunk];

const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    : compose;

const enhancer = composeEnhancers(applyMiddleware(...middleware));

const makeStore: MakeStore<Store<ReducerType, AnyAction>> = () =>
  createStore(rootReducer, enhancer);

export const wrapper = createWrapper(makeStore);

 

마지막으로 잘 동작하는지 확인해보겠습니다.

npm run dev

위 명령어로 서버를 실행하고 localhost:3000으로 접속해봅니다.

 

name의 값을 guest로 초기화해두었기 때문에 guest 라고 출력되는 것을 확인할 수 있습니다. 여기에 jay 라고 입력하고 submit 버튼을 클릭해보겠습니다. 

SET_NAME action이 실행되었고, guest가 jay로 바뀐 것을 확인할 수 있습니다.

 

 

소스는 아래 github에 올려두었습니다.

 

GitHub - hjleesm/nextjs-typescript-redux

Contribute to hjleesm/nextjs-typescript-redux development by creating an account on GitHub.

github.com

 

 

참고:

 

Add Redux to a Next JS App

In this article, I will go over how you can configure your Next JS application to use redux. First s...

dev.to

 

 

4. redux-thunk · GitBook

4. redux-thunk 소개 redux-thunk는 리덕스에서 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어입니다. 이 미들웨어를 사용하면 액션 객체가 아닌 함수를 디스패치 할 수 있습니다. redux-thunk는

react.vlpt.us

 

반응형