Archive

React에서 Typescript 사용하기 - 3

|

React에서 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