# redux - typeScript 사용법

# package.json 설치

yarn add redux react-redux @types/react-redux

# 액션 type 선언

const INCREASE = "counter/INCREASE" as const;
const DECREASE = "counter/DECREASE" as const;
const INCREASE_BY = "counter/INCREASE_BY" as const;

as const 이 문법을 쓰면 액션 함수를 통해 액션 객체를 만들면 type의 typescript 타입이 string이 아닌 실제값을 가리킨다.

const str = "heelo";
const strConst = "hello world" as const;

const object = { str }; // str: string;
const object2 = { strConst }; // strConst: 'hello world'

# 액션 생성 함수 선언

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

왠만하면 액션의 파라미터는 payload로 쓴다 FSA 규칙 준수

# 액션 객체 type 준비

type CounterAction =
  | ReturnType<typeof increase>
  | ReturnType<typeof decrease>
  | ReturnType<typeof increaseBy>;

여기서 사용 된 ReturnType 은 함수에서 반환하는 타입을 가져올 수 있게 해주는 유틸 타입입니다.

또는

type CounterAction =
  | ReturnType<typeof increase>
  | { type: typeof decrease; data: {...} };

우리가 이전에 액션의 type 값들을 선언 할 때 as const 라는 키워드를 사용했었지요? 만약 이 작업을 처리하지 않으면 ReturnType을 사용하게 됐을 때 type 의 타입이 무조건 string 으로 처리되어버립니다. 그렇게 되면 나중에 리듀서를 제대로 구현 할 수가 없어요.

# 상태의 타입과 상태의 초기값 선언

counter 모듈의 상태타입, 초기값 선언

type CounterState = {
  count: number;
};

const initialState: CounterState = {
  count: 0
};

type, interface 둘중하나만 쓴다

# 리듀서 작성

함수의 반환 타입에 상태의 타입을 넣는 것을 잊지 마세요. 이를 통하여 사소한 실수를 방지 할 수 있습니다.

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

export default counter;

# 루트 리듀서 반환

// src/modules/index.ts

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

const rootReducer = combineReducers({
  counter
});

export default rootReducer;

export type RootState = ReturnType<typeof rootReducer>;

RootState 라는 타입을 만들어서 내보내주어야 한다는 것 입니다. 이 타입은 추후 우리가 컨테이너 컴포넌트를 만들게 될 때 스토어에서 관리하고 있는 상태를 조회하기 위해서 useSelector를 사용 할 때 필요로 합니다.

# 카운터 프레젠테이션 컴포넌트

// src/components/Counter.tsx

import React from "react";

type CounterProps = {
  count: number;
  onIncrease: () => void;
  onDecrease: () => void;
  onIncreaseBy: (diff: number) => void;
};

function Counter({
  count,
  onIncrease,
  onDecrease,
  onIncreaseBy
}: CounterProps) {
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
      <button onClick={() => onIncreaseBy(5)}>+5</button>
    </div>
  );
}

export default Counter;

# 카운터 컨테이너 컴포넌트

useSelector 부분에서 state 의 타입을 RootState로 지정해서 사용한다는 것 외에는 없습니다.

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

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

  const onIncrease = () => {
    dispatch(increase());
  };

  const onDecrease = () => {
    dispatch(decrease());
  };

  const onIncreaseBy = (diff: number) => {
    dispatch(increaseBy(diff));
  };

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

export default CounterContainer;
Last Updated: 3/24/2021, 8:55:12 PM