[Typescript] TS + Redux === true

이찬형·2020년 7월 18일
1
post-thumbnail

Typescript 를 공부하면서 무수히 많은 빨간 줄을 보고 있습니다.. JS에선 잘 돌아가는 코드가 TS에선 에러가 되니 당황스러웠던 적도 많고 이해가 안 되는 부분도 많았어요. 그래도 이렇게 엄격하게 체크를 해주니 칙칙한 리액트 에러페이지를 훨씬 덜 보게 되는 것 같아요. 빨리 익숙해지고 싶습니다ㅠㅠㅠ

개발자님 !! 이 객체는 NULL 일 수도 있는걸요 ?

오늘은 기본적인 ReactRedux 를 사용한 상태관리 코드를 작성해보려고 해요. 정말정말 많이 쓰는 패턴이기에 어떤 구조로 짜야 예쁠지 공부해보도록 하겠습니다.

늘 했던 것 처럼, 카운터를 만들어봅시다!!

구조

보통 components 폴더와 routes 폴더도 생성해주지만 간단한 카운터만 만들 것이기 때문에 store 만 생성했습니다.

Redux 의 기본 구조인 actionCreatorreducer 를 만들어볼게요.

먼저 액션 생성 함수입니다!!
카운터이기 때문에 증가, 감소 액션이 필요하겠네요.

액션 생성 함수

src/store/actions/counterActions.ts

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

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

export default { increase, decrease, increaseBy };

짚고 넘어갈 부분은 as const 이거예요!!
타입 단언 중 Const Assertion 이라는 타입스크립트 문법인데 이렇게 선언을 해주면 INCREASE 변수에 다른 문자열이 들어오면 에러를 뿜어냅니다. 말 그대로 const 로 만드는거죠!!

이렇게 string 타입이 아닌 입력된 문자열이 박히게 됩니다.
다른 것은 크게 다른 것이 없으니 리듀서로 넘어가자구요.

리듀서

src/store/actions/counterReducer.ts

import counterActions from "../actions/counterActions";

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

type CounterState = {
  count: number;
};

const initialState: CounterState = {
  count: 0,
};

const count = (state: CounterState = initialState, action: CounterAction) => {
  switch (action.type) {
    case "counterActions/INCREASE":
      return { count: state.count + 1 };
    case "counterActions/DECREASE":
      return { count: state.count - 1 };
    case "counterActions/INCREASE_BY":
      return { count: state.count + action.payload };
    default:
      return state;
  }
};

export default count;

타입스크립트이기 때문에 리듀서에 들어갈 stateaction 의 타입을 알려줘야해요.

액션부터 해보죠!! 이 친구는 type 을 포함한 객체를 받아요. 따라서 올 수 있는 객체들을 알려주는 작업이 필요할 것 같네요.

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

그 부분이 여기입니다. ReturnType<typeof FUNC> 을 사용하게 되면 FUNC 이 리턴하는 값의 타입을 가져올 수 있어요.

액션에 우리가 받을 객체들이 예쁘게 들어가게 됩니다.

상태는 더 쉬워요!! Props 인터페이스 생성할 때 처럼 관리할 값들을 넣어주면 됩니다. 그냥 : number 로 해도 되지만 앞으로 객체를 사용할 일이 더 많을 것 같아 타입을 정의해 주었어요.

그 이후는 일반 리듀서와 똑같으니, index.js 로 가서 리듀서들을 합쳐봅시다.

src/store/reducers/counterReducer.ts

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

const rootReducer = combineReducers({ counter });

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

export type RootStore = ReturnType<typeof rootReducer>;

이 부분이 정말정말 중요해요. 나중에 useSelector 같은 함수로 스토어에 접근할 때, 스토어가 무엇인지 명시해주어야 하기 때문입니다.

rootReducer 를 최상단에 넣어줍시다.

src/index.tsx

const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

스토어 접근 및 사용

상태 관리 설정이 끝났습니다! 이제 Counter.tsx 컴포넌트에서 사용해보도록 할게요.

아까 중요하다고 했었던 RootStore 를 사용할 때가 왔어요. useSelector 로 스토어에 접근할 때 타입을 명시해줍시다.

/* .. */
import { RootStore } from "./store/reducers";
/* .. */
const count = useSelector((store: RootStore) => store.counter.count);
.
.

RootStore,ReturnType<typeof rootReducer> 에 의해 store 에 무엇이 있는지 알게 되었어요.

나머지는 똑같이 사용하면 되겠습니다!! 전체 코드에요.

src/Counter.tsx

import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootStore } from "./store/reducers";
import allActions from "./store/actions";

const Counter: React.FC<{}> = () => {
  const count = useSelector((store: RootStore) => store.counter.count);
  const handleIncrease = () => dispatch(allActions.counterActions.increase());
  const handleDecrease = () => dispatch(allActions.counterActions.decrease());
  const handleIncreaseBy = (diff: number) =>
    dispatch(allActions.counterActions.increaseBy(diff));
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={handleIncrease}>+1</button>
      <button onClick={handleDecrease}>-1</button>
      <button onClick={() => handleIncreaseBy(5)}>+5</button>
    </div>
  );
};

export default Counter;

끝 ?

기본적인 타입스크립트 환경에서의 리덕스 작성법을 공부해봤어요. 이제 미들웨어를 어떻게 작성할지 찾아볼 시간이네요ㅠㅠㅠ 뭔가 코드의 제약이 걸린다는 것이 은근히 재밌네요 ㅋㅋㅋㅋㅋ 앞으로 더 열심히 공부하겠습니다!!

파이팅 🧑🏻‍💻

profile
WEB / Security

0개의 댓글