Redux-thunk 02 | 웹 요청 비동기 작업 처리

Kate Jung·2022년 1월 2일
0

middlewares & libraries

목록 보기
7/17
post-thumbnail

📌 웹 요청 비동기 작업 처리

1. API 함수화 (lib/api.js)

axios 사용

  • API 호출 시, 주로 axios(Promise 기반 웹 클라이언트) 사용

  • 설치

    $ yarn add axios

함수화 이유

API 호출 함수 → 따로 작성 시, 가독성 ↑ & 유지보수 ↑

코드

import axios from 'axios';

export const getPost = (id) =>
  axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);

export const getUsers = (id) =>
  axios.get(`https://jsonplaceholder.typicode.com/users`);
  • export 사용 이유

    다른 파일에서 불러와 사용 가능

2. '로딩 상태 관리' 모듈 제작

  • 로딩 상태만 관리하는 리덕스 모듈 따로 생성
  1. 모듈 생성(modules/loading.js)
import { createAction, handleActions } from 'redux-actions';

const START_LOADING = 'loading/START_LOADING';
const FINISH_LOADING = 'loading/FINISH_LOADING';

// 요청을 위한 액션 타입을 payload로 설정 (예: "sample/GET_POST")
export const startLoading = createAction(
  START_LOADING,
  (requestType) => requestType,
);

export const finishLoading = createAction(
  FINISH_LOADING,
  (requestType) => requestType,
);

const initialState = {};

const loading = handleActions(
  {
    [START_LOADING]: (state, action) => ({
      ...state,
      [action.payload]: true,
    }),
    [FINISH_LOADING]: (state, action) => ({
      ...state,
      [action.payload]: false,
    }),
  },
  initialState,
);

export default loading;
  1. 루트 리듀서에 포함 시키기 (modules/index.is)
import { combineReducers } from 'redux';
import counter from './counter';
import sample from './sample';
**import loading from './loading';**

const rootReducer = combineReducers({
  counter,
  sample,
  **loading**,
});

export default rootReducer;
  1. api 요청 과정에 적용 (lib/createRequestThunk.js)

createRequestThunk.js 제작 후 적용

  1. container 컴포넌트에 적용 (containers/SampleContainer.js)

SampleContainer.js 제작 후 적용

3. 모듈(sample) 생성

◾ 액션 타입, thunk 함수, 초기 상태, 리듀서 생성(modules/sample.js)

  • 액션 타입 선언

    한 요청 당 세 개 만들기 → 시작, 성공, 실패

  • thunk 함수

    함수 내부 → 시작/성공/실패 시 다른 액션 디스패치

  • 초기 상태 선언

    로딩 상태: 로딩 모듈에서 관리

import { handleActions } from 'redux-actions';
import * as api from '../lib/api';
import createRequestThunk from '../lib/createRequestThunk';

// 액션 타입 선언
const GET_POST = 'sample/GET_POST';
const GET_POST_SUCCESS = 'sample/GET_POST_SUCCESS';

const GET_USERS = 'sample/GET_USERS';
const GET_USERS_SUCCESS = 'sample/GET_USERS_SUCCESS';

// thunk 함수 생성
// thunk 함수 내부 -> 시작/성공/실패 했을 때 다른 액션을 디스패치
export const getPost = createRequestThunk(GET_POST, api.getPost);
export const getUsers = createRequestThunk(GET_USERS, api.getUsers);

// 초기 상태 선언
const initialState = {
  post: null,
  users: null,
};

// 리듀서
const sample = handleActions(
  {
    [GET_POST_SUCCESS]: (state, action) => ({
      ...state,
      post: action.payload,
    }),
    [GET_USERS_SUCCESS]: (state, action) => ({
      ...state,
      users: action.payload,
    }),
  },
  initialState,
);

export default sample;

◾ Thunk 함수 (API 요청) 생성 유틸 함수 (lib/createRequestThunk.js)

  • 사용법

    createRequestThunk(액션 타입, api 요청 함수)

  • 코드

    import { startLoading, finishLoading } from '../modules/loading';
    
    export default function createRequestThunk(type, request) {
      // 액션 타입 정의 (성공 및 실패)
      const SUCCESS = `${type}_SUCCESS`;
      const FAILURE = `${type}_FAILURE`;
    
      return (params) => async (dispatch) => {
        dispatch({ type }); // 시작됨
        dispatch(startLoading(type));
    
        try {
          const response = await request(params);
          dispatch({
            type: SUCCESS,
            payload: response.data,
          }); // 성공
          dispatch(finishLoading(type));
        } catch (e) {
          dispatch({
            type: FAILURE,
            payload: e,
            error: true,
          }); // 에러 발생
          dispatch(startLoading(type));
          throw e; // 나중에 컴포넌트 단에서 에러 조회 가능
        }
      };
    }

4. 해당 리듀서 → 루트 리듀서에 포함 (modules/index.js)

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

const rootReducer = combineReducers({
  counter,
  **sample**,
});

export default rootReducer;

5. 데이터 형식(구조) 확인

6. 프레젠테이셔널 컴포넌트 작성 (components/Sample.js)

  • 중요: 데이터 불러와서 렌더링 시 → 유효성 검사 필수

    이유: 해당 데이터를 사용 시 → 데이터가 없는 상태라면, js 오류 발생.

    • 예시
        {!loadingPost && post && (
                  <div>
                    <h3>{post.title}</h3>
                    <h3>{post.body}</h3>
                  </div>
        )}

◾ 코드

import React from 'react';

const Sample = ({ loadingPost, loadingUsers, post, users }) => {
  return (
    <div>
      <section>
        <h1>포스트</h1>
        {loadingPost && '로딩 중...'}
        {!loadingPost && post && (
          <div>
            <h3>{post.title}</h3>
            <h3>{post.body}</h3>
          </div>
        )}
      </section>
      <hr />
      <section>
        <h1>사용자 목록</h1>
        {loadingUsers && '로딩 중...'}
        {!loadingUsers && users && (
          <ul>
            {users.map((user) => (
              <li key={user.id}>
                {user.username} ({user.email})
              </li>
            ))}
          </ul>
        )}
      </section>
    </div>
  );
};

export default Sample;

7. 컨테이너 컴포넌트 작성 (containers/SampleContainer.js)

import React from 'react';
import { connect } from 'react-redux';
import Sample from '../components/Sample';
import { getPost, getUsers } from '../modules/sample';

const { useEffect } = React;
const SampleContainer = ({
  getPost,
  getUsers,
  post,
  users,
  loadingPost,
  loadingUsers,
}) => {
  // 클래스 컴포넌트였다면 componentDidMount
  useEffect(() => {
    getPost(1);
    getUsers(1);
  }, [getPost, getUsers]);

  return (
    <Sample
      post={post}
      users={users}
      loadingPost={loadingPost}
      loadingUsers={loadingUsers}
    />
  );
};

export default connect(
  ({ sample, loading }) => ({
    post: sample.post,
    users: sample.users,
    loadingPost: loading['sample/GET_POST'],
    loadingUsers: loading['sample/GET_USERS'],
  }),
  { getPost, getUsers },
)(SampleContainer);

8. App.js에 컨테이너 렌더링

import React from 'react';
**import SampleContainer from './containers/SampleContainer';**

const App = () => {
  return (
    <div>
      <**SampleContainer** />
    </div>
  );
};

9. 실패 케이스 관리

  1. _FAILURE 가 붙은 액션 → 리듀서에서 처리

  2. 컨테이너 컴포넌트 → try/catch 구문 사용 (에러 값 조회)

    useEffect(() => {
        // [에러 값 조회]
        // useEffect에 파라미터로 넣는 함수는 async로 할 수 없기 때문에
        // 그 내부에서 async 함수 선언 & 호출
        const fn = async () => {
          try {
            await getPost(1);
            await getUsers(1);
          } catch (e) {
            console.log(e); // 에러 조회
          }
        };
        fn();
      }, [getPost, getUsers]);

참고

  • 벨로퍼트_'리액트를 다루는 기술' 책
profile
복습 목적 블로그 입니다.

0개의 댓글