React에서 Typescript 사용하기 - 3
04 Sep 2021 | TypescriptReact에서 Typescript 사용하기 - 3
1. reducer
- 액션 타입을 정의한다
- 각 액션에 대한 인터페이스를 정의한다
- 
    액션이 많을 경우 type으로 묶는다 
- initialState에 대한 type을 정의한다
- reducer의 매개변수인 state, action에 대한 타입을 설정하고 return type도 state와 동일하게
- Action 타입과 Action에 대한 인터페이스는 프로젝트 성격에 맞게 따로 빼서 저장할 수도 있다
// 액션타입 정의
export enum ActionType {
  SEARCH_REPOSITORIES = 'search_repositories',
  SEARCH_REPOSITORIES_SUCCESS = 'search_repositories_success',
  SEARCH_REPOSITORIES_ERROR = 'search_repositories_error'
}
// 액션에 대한 인터페이스
interface SearchRepositoriesAction {
  type: ActionType.SEARCH_REPOSITORIES;
}
interface SearchRepositoriesSuccessAction {
  type: ActionType.SEARCH_REPOSITORIES_SUCCESS;
  payload: string[];
}
interface SearchRepositoriesErrorAction {
  type: ActionType.SEARCH_REPOSITORIES_ERROR;
  payload: string;
}
// 위 3개를 하나로 묶어서 사용하면 편리
export type Action = 
  | SearchRepositoriesAction 
  | SearchRepositoriesSuccessAction 
  | SearchRepositoriesErrorAction;
// state에 대한 타입 정의
interface RepositoriesState {
  loading: boolean;
  error: string | null;
  data: string[];
}
const initialState = {
  loading: false,
  error: null,
  data: [],
};
const reducer = (
  state: RepositoriesState = initialState, // state 타입 설정
  action: Action // action 타입 설정
  ): RepositoriesState => { // retrun 타입 설정
  switch (action.type) {
    case ActionType.SEARCH_REPOSITORIES:
      return {
        loading: true,
        error: null,
        data: [],
      }
    case ActionType.SEARCH_REPOSITORIES_SUCCESS:
      return {
        loading: false,
        error: null,
        data: action.payload,
      }
    case ActionType.SEARCH_REPOSITORIES_ERROR:
      return {
        loading: false,
        error: action.payload,
        data: [],
      }
    default:
      return state;
  }
};
export default reducer;
2. action
- redux-thunk 사용
- dispatch의 타입은 redux의 Dispatch, 제네릭으로 들어갈 action 타입을 넣는다
import axios from 'axios';
import { Dispatch } from 'redux';
import { ActionType } from '../action-types';
import { Action } from '../actions';
export const searchRepositories = (term: string) => {
  return async (dispatch: Dispatch<Action>) => {
    dispatch({
      type: ActionType.SEARCH_REPOSITORIES
    });
    try {
      const { data } = await axios.get('https://registry.npmjs.org/-/v1/search', {
        params: {
          text: term
        }
      });
      const names = data.objects.map((result: any) => {
        return result.package.name;
      });
      dispatch({
        type: ActionType.SEARCH_REPOSITORIES_SUCCESS,
        payload: names
      });
    } catch (err: any) {
      dispatch({
        type: ActionType.SEARCH_REPOSITORIES_ERROR,
        payload: err.message
      });
    }
  };
};
3. store
- react와 같다
// app.tsx
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import reducers from "./reducers";
import { Provider } from 'react-redux';
import { store } from '../state';
import RepositoriesList from './RepositoriesList';
export const store = createStore(reducers, {}, applyMiddleware(thunk));
function App() {
  return (
    <Provider store={store}>
      <div>
		{/* ... */}	
      </div>
    </Provider>
  )
}
export default App;
4. useSelector
- useSelector의 state => state.{name}을 typescript가 인식하지 못함
- react-redux 패키지의 TypedUseSelectorHook을 사용하여 custom hook을 만들 수 있다
- TypedSelectorHook의 제네릭에 RootState를 설정하는데 combineReducers에서 묶어준 reducer들을 ReturnType으로 넣어준다
import { combineReducers } from "redux";
import repositoriesReducer from "./repositoriesReducer";
const reducers = combineReducers({
  repositories: repositoriesReducer,
});
export default reducers;
// RootState를 설정하여 TypedUseSelectorHook에서 사용한다
export type RootState = ReturnType<typeof reducers>;
// useTypedSelector.ts
import { useSelector, TypedUseSelectorHook } from "react-redux";
import { RootState } from "../state";
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
// useTypedSelector 사용
import { useTypedSelector } from '../hooks/useTypedSelector';
const RepositoriesList:React.FC = () => {
  const { searchRepositories } = useActions();
  const { data, error, loading } = useTypedSelector(state => state.repositories);
  
  return (
    <div>
		{/*...*/}
    </div>
  )
}
export default RepositoriesList;
5. +보너스 액션 dispatch시 편리하게 사용하기
- 액션들을 한데 묶어서 편리하게 사용할 수 있다
- redux 패키지의 bindActionCreators를 사용한다
- 이를 이용해 custom hook을 만들 수 있다
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../state'; // 액션들
export const useActions = () => {
  const dispatch = useDispatch();
  return bindActionCreators(actionCreators, dispatch);
  // bindActionCreators 사용하면 dispatch에 액션이 담긴 함수를 반환
  // { searchRepositories: dispatch(searchRepositories}
};
// 컴포넌트에서 다음과 같이 사용할 수 있다
const { searchRepositories } = useActions();
searchRepositories(value);
참고 자료
udemy - React and Typescript: Build a Portfolio Project
