next + redux + typescript 간단한 예제 만들어보기

typescript를 복습하기 위해 기본적인 예제를 만들어보는 시간
next.js, redux, Typescript 를 사용하였습니다.
예제 생성은 npx create-next-app --example with-typescript with-typescript-app 사용했습니다.

폴더 구조

├── components
│   └── Counter.tsx
├── containers
│   └── CounterContainer.tsx
├── hooks
│   └── useCounter.tsx
├── modules
│   ├── counter.ts
│   └── index.ts
├── pages
│   ├── _app.tsx
│   ├── api
│   └── index.tsx
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   └── globals.css
└── tsconfig.json
  • components
    UI 로직만 return 하는 컴포넌트
  • containers
    리덕스와 관련된 비즈니스 로직을 처리하는 컴포넌트
  • hooks
    커스텀 Hook 폴더
  • modules
    리덕스 ducks 패턴을 이용해 하나의 파일에 리듀서 관리
  • pages
    next에서 사용하는 pages 폴더로 라우터 기능을 지원한다

modules/counter.ts 리덕스 작성

1. action type 작성

// action type
const INCREASE = <const>'counter/INCREASE'
const DECREASE = <const>'counter/DECREASE'
const INCREASE_BY = <const>'counter/INCREASE_BY'

<const>의 뜻은 타입 추론을 할 수 있도록 하기 위한 것이다. 만약 <const> 제너릭을 사용하지 않고 그대로 놔뒀을 경우 type을 string으로 인식하게 된다.

2. action function 작성

// action function
export const increase = () => ({type: INCREASE})
export const decrease = () => ({type: DECREASE})
export const increaseBy = (diff: number) => ({type: INCREASE_BY, payload: diff})

3. initialState 및 counter reducer 작성

//initialStateType
interface initialStateType {
   count: number
}

//initialState
const intialState = {count: 0}

function counter(state: initialStateType = intialState, action: CounterAction) {
   switch (action.type) {
      case INCREASE:
         return {state: state.count + 1}
      case DECREASE:
         return {state: state.count - 1}
      case INCREASE_BY:
         return {state: state.count + action.payload}
      default:
         return state;
   }
}
export default counter

4. 프로젝트에 리덕스 적용

modules/index.ts

import {combineReducers} from "redux";
import counter from './counter';

export type RootState = ReturnType<typeof rootReducer>;

const rootReducer = combineReducers({counter})

export default rootReducer;

pages/_app.tsx

import '../styles/globals.css'
import type {AppProps} from 'next/app'
import {Provider} from "react-redux";
import {createStore} from "redux";
import rootReducer from "../modules";

const store = createStore(rootReducer)

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

export default MyApp

components

1. Counter.tsx 작성

import React from 'react';

interface TypeProps {
   count: number
   increase: () => void
   decrease: () => void
   increaseBy: (diff: number) => void
}

const Counter = ({count, increase, decrease, increaseBy}:TypeProps) => {
   return (
      <>
         <h1>{count}</h1>
         <button onClick={increase}>+</button>
         <button onClick={decrease}>-</button>
         <button onClick={() => increaseBy(5)}>increaseBy</button>
      </>
   );
};

export default Counter;

props로 전달받은 인자들만을 이용해 순수하게 UI 로직만 있는 코드를 작성합니다.

container

1. CounterContainer.tsx 작성

import React from 'react';
import Counter from "../components/Counter";
import {RootState} from "../modules";
import {useDispatch, useSelector} from "react-redux";
import {decrease, increase, increaseBy} from "../modules/counter";

const CounterContainer = () => {
   const count = useSelector((state: RootState) => state.counter.count)
   const dispatch = useDispatch();

   const onIncrease = () => {
      dispatch(increase());
   }
   const onDecrease = () => {
      dispatch(decrease())
   }
   const onIncreaseBy = () => {
      dispatch(increaseBy(5))
   }

   return (
      <Counter count={count} increase={onIncrease} decrease={onDecrease} increaseBy={onIncreaseBy} />
   );
};

export default CounterContainer;

커스텀 훅으로 프리젠테이션, 컨테이너 컴포넌트 한번에

프리젠테이션 컴포넌트와 컨테이너 컴포넌트를 분리하지 않고 커스텀 훅을 만들어 한 번에 처리할 수도 있다.
프리젠테이션, 컨테이너 컴포넌트 차이점은 링크에 잘 나와있다!

hooks/useCounter.tsx

import React from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../modules";
import {decrease, increase, increaseBy} from "../modules/counter";

const UseCounter = () => {
   const dispatch = useDispatch();
   const count = useSelector((state: RootState) => state.counter.count);
   const onIncrease = () => {
      dispatch(increase())
   }
   const onDecrease = () => {
      dispatch(decrease())
   }
   const onIncreaseBy = (diff:number) => {
      dispatch(increaseBy(diff))
   }

   return {
      count, onIncrease, onDecrease, onIncreaseBy
   }
};

export default UseCounter;

기존과 같이 props로 전달해준 것이 아닌 hook을 만들어 해당하는 값들을 return 받아 Counter 컴포넌트에서 바로 사용할 수 있다.

components/Counter.tsx

import React from 'react';
import useCounter from '../hooks/useCounter'
const Counter = () => {
   const {count, onIncrease, onDecrease, onIncreaseBy} = useCounter();

   return (
      <>
         <h1>{count}</h1>
         <button onClick={onIncrease}>+</button>
         <button onClick={onDecrease}>-</button>
         <button onClick={() => onIncreaseBy(5)}>increaseBy</button>
      </>
   );
};

export default Counter

좋은 웹페이지 즐겨찾기