벨로퍼트님 벨로그를 참고하여 작성하였습니다.
리덕스를 적용한 리액트 프로젝트 만드는 커맨드
$ npx create-react-app ts-react-redux-tutorial --typescript
$ cd ts-react-redux-tutorial
$ npm install redux react-redux @types/react-redux
redux의 경우엔 자체적으로 TypeScript 지원이 된다. 하지만 react-redux의 경우 그렇지 않기 때문에 패키지명 앞에 @types를 붙인 패키지를 설치해야한다.
Ducks 패턴을 사용하여 한 파일에 액션의 Type, 액션 생성함수, 리듀서를 작성
아래는 모듈 작성 순서
1. 액션 type 선언 (리덕스 액션 안에 들어가게 될 type 선언)
2. 액션 생성 함수 선언
3. 액션 객체들에 대한 type 준비하기(Typescript의 type)
4. 상태의 타입과 상태의 초기값 선언하기
5. 리듀서 작성하기
커스텀 hook을 이용하는 이유는 기존 방식과 다르게 좀 더 간결하게 코드를 작성할 수 있다는 장점이 있어서이다.
설명 추가 예정
modules/counter.ts
//액션 타입 선언
const INCREASE = "counter/INCREASE" as const;
const DECREASE = "counter/DECREASE" as const;
const INCREASE_BY = "counter/INCREASE_BY" as const;
as const는 const assertions라는 타입스크립트 문법이다.
as const를 사용함으로써, 나중에 액션 생성함수를 통해 액션 객체를 만들 때 typescript 타입이 string이 되지 않고 실제 값이 된다.
위에 코드에서 실제값은 "counter/INCREASE" ...
//액션 생성 함수
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
export const increaseBy = (diff: number) => ({
type: INCREASE_BY,
payload: diff,
});
//액션 객체들에 대한 type 준비하기
type CounterAction =
| ReturnType<typeof increase>
| ReturnType<typeof decrease>
| ReturnType<typeof increaseBy>;
나중에 리듀서 작성할 때 action 파라미터의 ㅏ입을 설정하기 위한 코드
//상태에 관한 type과 상태 생성 코드
type CounterState = {
count: number;
};
const initialState: CounterState = {
count: 0,
};
//reducer 함수
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;
------------------------------------------------------------------------
modules/index.ts
import { combineReducers } from 'redux';
import counter from './counter';
const rootReducer = combineReducers({
counter
});
export default rootReducer;
export type RootState = ReturnType<typeof rootReducer>;
-------------------------------------------------------------------------
hooks/useCounter.tsx
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../modules';
import { increase, decrease, increaseBy } from '../modules/counter';
import { useCallback } from 'react';
export default function useCounter() {
const count = useSelector((state: RootState) => state.counter.count);
const dispatch = useDispatch();
const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
const onIncreaseBy = useCallback(
(diff: number) => dispatch(increaseBy(diff)),
[dispatch]
);
return {
count,
onIncrease,
onDecrease,
onIncreaseBy
};
}
---------------------------------------------------------------------
components/Counter.tsx
import React from 'react';
import useCounter from '../hooks/useCounter';
function Counter() {
const { count, onIncrease, onDecrease, onIncreaseBy } = useCounter();
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;