본문 바로가기
React/TS

React에서 TypeScript 사용하기 - Redux Middleware(Thunk, Saga)

by 강깅꽁 2020. 10. 28.

비동기 작업을 처리할 때 주로 redux middleware를 사용하게 됩니다.

이때 주로 사용하는 library는 redux-thunk와 redux-saga입니다. 

그래서 이번 글에서는 두 개의 library를 TS로 어떻게 사용하면 될지 소개합니다. 

 

Tip. 비동기 요청을 통해 가져오는 데이터의 경우 직접 타입을 하나 하나 정해주기 어려운 경우가 있습니다.

그럴 때 데이터를 넣어주면 자동으로 타입으로 변환해주는 서비스를 이용하면 편하게 타입을 만들 수 있습니다.

ex. { "id": 1, "text": "this is text", "done": false } => export interface Todo{ id:   number; text: string; done: boolean; }

quicktype.io/

 

비동기 작업은 다음과 같은 3가지 경우가 있다고 볼 수 있습니다.

1. 비동기 작업의 요청(loading)

2. 비동기 작업의 성공(success)

3. 비동기 작업의 실패(failure)

 

Redux Thunk Middleware with JS

더보기

먼저 JS로 어떻게 사용하는지 알아보겠습니다. 
비동기 작업을 관리하기 위해 loading, success, failure와 관련된 액션 타입과 액션 생성 함수를 생성해줍니다.
그리고 thunk creator를 만들어서 비동기 관련 액션 발생 및 서버에 요청 작업을 실행해 줍니다. 

import { createAction, handleActions } from "redux-actions"
import { getUserProfile } from "../api/github"

// 액션 타입
const GITHUB_USER_PROFILE = "test/GITHUB_USER_PROFILE"
const GITHUB_USER_PROFILE_SUCCESS = "test/GITHUB_USER_PROFILE_SUCCESS"
const GITHUB_USER_PROFILE_FAILURE = "test/GITHUB_USER_PROFILE_FAILURE"

// 액션 생성 함수
export const githubUserProfile = createAction(GITHUB_USER_PROFILE)
export const githubUserProfileSuccess = createAction(GITHUB_USER_PROFILE_SUCCESS, data => data)
export const githubUserProfileFailure = createAction(GITHUB_USER_PROFILE_FAILURE, error => error)

// Thunk Creator
export const githubUserProfileThunk = username => async dispatch => {
  dispatch(githubUserProfile())
  try {
    const userProfile = await getUserProfile(username)
    dispatch(githubUserProfileSuccess(userProfile))
  } catch (error) {
    dispatch(githubUserProfileFailure(error))
  }
}

const initialState = {
  userProfile: {
    loading: false,
    data: null,
    error: null,
  },
}

const test = handleActions(
  {
    [GITHUB_USER_PROFILE]: state => ({
      ...state,
      userProfile: {
        loading: true,
        data: null,
        error: null,
      },
    }),
    [GITHUB_USER_PROFILE_SUCCESS]: (state, action) => ({
      ...state,
      userProfile: {
        loading: false,
        data: action.payload,
        error: null,
      },
    }),
    [GITHUB_USER_PROFILE_FAILURE]: (state, action) => ({
      ...state,
      userProfile: {
        loading: false,
        data: null,
        error: action.payload,
      },
    }),
  },
  initialState
)

export default test


Redux Thunk Middleware with TS

TS로 변환된 코드는 다음과 같습니다.

typesafe-actions에서 createAction으로 액션 생성 함수를 하나씩 만들어도 되지만 createAsyncAction으로 비동기와 관련된 생성 함수들을 간편하게 만들 수 있습니다. 

createAsyncAction에서 설정하는 제네릭 타입은 만들어질 각 생성 함수가 리턴하는 액션 객체의 페이로드 타입 입니다.

typesafe-actions library를 활용하는게 어려우시 다면 아래 글을 참고해주세요.

webigotr.tistory.com/300

import { AxiosError } from "axios";
import { Dispatch } from "redux";
import { ActionType, createAsyncAction, createReducer } from "typesafe-actions";
import { getUserProfile, GithubProfile } from "../api/github"

const GITHUB_USER_PROFILE = "test/GITHUB_USER_PROFILE"
const GITHUB_USER_PROFILE_SUCCESS = "test/GITHUB_USER_PROFILE_SUCCESS"
const GITHUB_USER_PROFILE_FAILURE = "test/GITHUB_USER_PROFILE_FAILURE"

export const getUserProfileAsync = createAsyncAction(
    GITHUB_USER_PROFILE,
    GITHUB_USER_PROFILE_SUCCESS,
    GITHUB_USER_PROFILE_FAILURE
)<undefined, GithubProfile, AxiosError>();

export const githubUserProfileThunk = (username: string) => async (dispatch: Dispatch) => {
  const {request, success, failure} = getUserProfileAsync;
  dispatch(request());
  try {
    const userProfile = await getUserProfile(username)
    dispatch(success(userProfile));
  } catch (error) {
    dispatch(failure(error));
  }
}

export type GithubAction = ActionType<typeof getUserProfileAsync>
export type GithubState = {
    userProfile: {
        loading: boolean;
        data: GithubProfile | null;
        error: Error | null;
    }
}

const initialState = {
  userProfile: {
    loading: false,
    data: null,
    error: null,
  },
}

const test = createReducer<GithubState, GithubAction>(initialState,
  {
    [GITHUB_USER_PROFILE]: state => ({
      ...state,
      userProfile: {
        loading: true,
        data: null,
        error: null,
      },
    }),
    [GITHUB_USER_PROFILE_SUCCESS]: (state, action) => ({
      ...state,
      userProfile: {
        loading: false,
        data: action.payload,
        error: null,
      },
    }),
    [GITHUB_USER_PROFILE_FAILURE]: (state, action) => ({
      ...state,
      userProfile: {
        loading: false,
        data: null,
        error: action.payload,
      },
    }),
  }
)

export default test