[react] 불필요한 api call 줄이기

Suyeon·2021년 2월 11일
5

React

목록 보기
19/26
post-thumbnail

axioscancellationlodash-debounce에 대해서 알아보자 🤓

Problem

input을 통해서 사용자로부터 query를 받고, 그 query를 이용해서 api와 통신하는 경우, 우리는 일반적으로 inputonChange를 걸고 서버에 api call을 보내게 된다. 하지만 이 경우, 사용자의 모든 입력에 따라서 api call을 보내기 때문에 불필요하게 많은 request를 보내게 된다.

How to solve?

axios가 제공하는 canceltoken을 사용하거나, lodash-debounce 라이브러리를 사용해서 아주 쉽게 해결할 수 있다!

1️⃣ Axios Cancellation

Axios의 cancelTocken을 사용해서 request를 캔슬할 수 있다.
(레퍼런스는 여기 참고)

Syntax

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken((c) => cancel = c })
});

// cancel the request
cancel();

cancellationTocken을 생성하기 위해서는 아래 두가지 방법중 선택하면 된다.
1. CancelToken.source을 사용해서 생성
2. executor functionCancelToken constructor에 passing해서 생성

Example 1

useEffect(() => {
const request = Axios.CancelToken.source() // (*)

const fetchPost = async () => {
  try {
    const response = await Axios.get(`endpointURL`, {
      cancelToken: request.token, // (*)
    })
    setPost(response.data)
    setIsLoading(false)
  } catch (err) {
    console.log('There was a problem or request was cancelled.')
  }
}
fetchPost()

return () => request.cancel() // (*)
}, [])

Example 2

아래의 코드는 두번째 방법을 사용했다.

function useFetch(query: string, pageNum: number) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(false);
  const [list, setList] = useState<MovieList>([]);
  const [hasMore, setHasMore] = useState(false);
  
  const sendQuery = useCallback(
    async (query: string): Promise<any> => {
      const CancelToken = axios.CancelToken; // (*)
      let cancel: () => void;  // (*)
  
      if (query === '') return;
  
      try {
        await setIsLoading(true);
        let res = await axios.get(
          `${URL}?s=${query}&page=${pageNum}&apikey=${API_KEY}`,
          {
            cancelToken: new CancelToken(c => (cancel = c)),  // (*)
          }
        );
        const data: MovieList = await res?.data?.Search;

        if (data) {
          await setList(
            (prev: MovieList): MovieList => {
              return [...new Set([...prev, ...data])];
            }
          );
        }
        await setHasMore(data?.length > 0);
        setIsLoading(false);
      } catch (error) {
        if (axios.isCancel(error)) return;
        setError(error);
      }
      return () => cancel(); // (*)
    },
    [pageNum]
  );
  useEffect(() => {
    setList([]);
  }, [query]);
  useEffect(() => {
    sendQuery(query);
  }, [query, pageNum, sendQuery]);
  return { isLoading, error, list, hasMore };
}
export default useFetch;

Lodash-debounce

debounce()는 이벤트에 의해 특정 함수가 여러번 반복적으로 실행될 수 경우, 정해진 지연시간동안 반복된 호출을 1번만 호출하도록 제어한다.

Syntax

_.debounce(callback, delay)

Lodash-debounce는 setTimeout을 기반으로 작동한다. 따라서 컴포넌트가 렌더링 될때마다 새로운 setTimeout callback이 생성된다. 따라서 debounced callback을 레퍼런스로 저장해야 하는데, 우리는 useCallback 혹은 useRef을 사용할 수 있다.

따라서 리액트에서 Lodash-debounce를 사용하기 위해서, 아래 두가지중 한가지를 선택하면 된다.
1. useCallback을 사용
2. useRef을 사용

아래의 코드는 첫번째 방법을 사용했다.

import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { MovieList } from 'types/types';
import { debounce } from 'lodash';

const API_KEY: string | undefined = process.env.REACT_APP_API_KEY;
const URL: string = `http://www.omdbapi.com/`;

function useFetch(query: string, pageNum: number) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(false);
  const [list, setList] = useState<MovieList>([]);
  const [hasMore, setHasMore] = useState(false);

  const delayedQuery = useCallback( // (*)
    debounce((q: string) => sendQuery(q), 500),
    []
  );

  const sendQuery = useCallback(
    async (query: string): Promise<any> => {
      if (query === '') return;

      try {
        await setIsLoading(true);
        let res = await axios.get(
          `${URL}?s=${query}&page=${pageNum}&apikey=${API_KEY}`
        );
        const data: MovieList = await res?.data?.Search;
        if (data) {
          await setList(
            (prev: MovieList): MovieList => {
              return [...new Set([...prev, ...data])];
            }
          );
        }
        await setHasMore(data?.length > 0);
        setIsLoading(false);
      } catch (error) {
        setError(error);
      }
    },
    [pageNum]
  );

  useEffect(() => {
    setList([]);
  }, [query]);

  useEffect(() => {
    delayedQuery(query); // (*)
  }, [query, pageNum, delayedQuery]);

  return { isLoading, error, list, hasMore };
}

export default useFetch;
profile
Hello World.

0개의 댓글