본문 바로가기
React/TS

React에서 TypeScript 사용하기 - Redux

by 강깅꽁 2020. 10. 26.

TypeScript는 필수 요소가 아니기 때문에 라이버러리를 쓸 때 Type을 지원해줄 수도 있고 안해줄 수도 있습니다.

공식적으로 지원해주는 경우도 있지만 3rd party library형식으로 커뮤니티에서 지원해주는 경우가 있는데

이럴 때 다음 사이트에서 쉽게 확인이 가능합니다. 

 

www.typescriptlang.org/dt/search?search=react-redux

 

Search for typed packages

Find npm packages that have type declarations, either bundled or on Definitely Typed.

www.typescriptlang.org

 

Redux Module Ducks 패턴 

ducks 패턴은 redux module을 작성할 때 액션 타입, 액션 생성 함수, 리듀서를 한 파일에 담는 것을 말합니다.

 

// 액션 타입 선언
const INCREASE = 'counter/INCREASE'

// 액션 생성 함수
/* 
	액션 타입을 위와 같이 선언하면 increase 액션 생성 함수가 리턴 하는 액션 객체의 type이 string type입니다.
	const increase: () => { type: string; }
    그 이유는 변수의 선언과 동시에 할당하게 되면 할당된 값의 타입을 자동으로 변수의 타입으로 사용하게 됩니다.
    즉 변수 INCREASE의 타입은 string type이 됩니다.
    하지만 우리가 정확한 타입 추론을 사용하기 위해서는 타입을 'counter/INCREASE'와 같이 리터럴 타입으로 해야 됩니다.
*/
export const increase = () => ({ type: INCREASE })

 

액션 타입의 정확한 타입 추론을 위한 해결 방법

//  액션 타입 선언
// const INCREASE: 'counter/INCREASE' = 'counter/INCREASE'와 같음
const INCREASE = 'counter/INCREASE' as const;

// 액션 생성 함수 increase에 마우스를 올려 보면 다음과 같이 정확한 타입 추론을 해줍니다.
// const increase: () => { type: "counter/INCREASE"; }
export const increase = () => ({ type: INCREASE })

 

useReducer를 사용할 때 처럼 redcuer함수는 state와 action 파라미터를 가지는데 각각의 타입을 선언해주어야 합니다.

참고: webigotr.tistory.com/285?category=413871

 

액션 객체의 타입을 간단하게 설정해주는 방법은

TS에 제공하는 유틸 타입인 ReturnType을 사용하는 것입니다. 

ReturnType은 함수 반환 값의 타입을 가져옵니다.

www.typescriptlang.org/docs/handbook/utility-types.html

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


// 실제 적용되는 타입 type CounterAction = { type: "counter/INCREASE";}
type CounterAction = ReturnType<typeof increase>;

/* 
똑같은 타입이지만 다른 선언 방식
type CounterAction = { type: typeof INCREASE}
type CounterAction = { type: 'counter/INCREASE'}
*/ 

 

전부 작성된 redux module

src/modules/counter.ts

const INCREASE = 'counter/INCREASE' as const;

export const increase = () => ({ type: INCREASE })

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

type CounterAction = ReturnType<typeof increase>

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

 

rootReducer 만들기

src/modules/index.ts

 

ReturnType을 통해 rootReducer의 반환 값에 대한 타입을 가져올 수 있는데 이것을 RootState에 저장합니다.

나중에 컴포넌트에서 useSelector를 사용하여 스토어에서 가져오는 값의 타입이 RotoState입니다. 

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

const rootReducer = combineReducers({
    counter,
})

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

 

src/index.tsx

redux 설정

import {Provider} from 'react-redux';
import {createStore} from 'redux';
import rootReducer from './modules';

const store = createStore(rootReducer);
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

 

리덕스 관련 코드는 작성되었으니 컴포넌트 코드를 작성합니다.

 

src/containers/CounterContainer.tsx

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

export default function CounterContainer() {
    const count = useSelector( (state: RootState) => state.counter.count);
    const dispatch = useDispatch();
    const onIncrease = () => {
        dispatch(increase());
    }

    return (
        <div>
            <Counter count={count} onIncrease={onIncrease} />
        </div>
    )
}

src/components/Counter.tsx

import React from 'react'

type CounterProps = {
    count: number;
    onIncrease(): void;
}

export default function Counter({count, onIncrease}: CounterProps) {
    return (
        <div>
            <p>{count}</p>
            <button onClick={onIncrease}>+1</button>
        </div>
    )
}

 

src/App.tsx

import React from 'react';
import CounterContainer from './containers/CounterContainer';

function App() {
  return (
    <CounterContainer />
  );
}

export default App;

 

이상으로 Redux에서 TS를 적용하는 방법과 간단한 예제를 구현하기 위한 컴포넌트들의 코드를 알아보았습니다. 

TS를 간단하게 사용해본 후기로는 타입 추론이 가능하니까 IDE에서 어떤 속성이나 메서드가 필요한지 계속 알려주는게 편했습니다.

그리고 나중에 다른 개발자들과 협업 할 때에도 실수를 최소화 하고 코드의 의도를 파악할 수 있겠다고 느껴집니다.