본문 바로가기
React.js

React Debounce

by Zih0 2022. 1. 26.

 

검색 기능

요즘 검색 기능을 보면 대부분의 사이트에서 글자 입력마다 결과가 바로바로 보이는 것을 확인할 수 있습니다.

해당 기능을 단순히 input의 onChange마다 API 콜을 하게된다면 비효율적으로 많은 API 콜이 발생하게 됩니다.

특히 API 요청 수의 제한이 있거나 수에 따라 결제가 이루어진다면 해당 방식은 문제가 발생하게 됩니다. 

 

아래는 코엑스를 입력했을 때, 글자 하나하나 API 요청을 하고 있는 모습입니다.

안좋은 예시

 

 

Debounce

이를 해결하기 위해선 debounce라는 개념을 알아야합니다!

debounce는 연속해서 함수를 호출 했을 때, 마지막 함수만 호출하도록 하는 방법입니다.

 

// 검색어 입력을 받는 state
const [searchAddress, setSearchAddress] = useState("");
 
// debounced 된 검색어 state
const [debouncedAddress, setDebouncedAddress] = useState("");
  
// 지도 검색 결과를 담을 state
const [locationList, setLocationList] = useState<ILocationItems[]>([]);

// input 값을 searchAddress state에 저장시켜주는 함수
function handleChangeSearch(e: React.ChangeEvent<HTMLInputElement>) {
    const {
      target: { value },
    } = e;

    setSearchAddress(value);
  }

// debounce
// useEffect를 활용하여, searchAddress state가 변할 때마다
// 200ms 이후 debouncedAddress state에 저장되도록. 
useEffect(() => {
  const timer = setTimeout(() => {
    return setDebouncedAddress(searchAddress);
  }, 200);

  // 200ms 전에 searchAddress가 수정되면 clear
  return () => {
    clearTimeout(timer);
  };
}, [searchAddress]);

// 카카오 지도 검색 결과를 저장하는 함수
async function handleSearchAddress(searchAddress: string) {
  const data = await kakaoLocalSearchApi(searchAddress);

  setLocationList(data);
}

// debouncedAddress state가 바뀔 때마다 카카오 지도 검색 API 호출
useEffect(() => {
  // 빈 값은 전달하지 않기 위해, length > 0 조건 추가
  if (debouncedAddress.length > 0) {
    handleSearchAddress(debouncedAddress);
  }
}, [debouncedAddress]);

 

위의 방식으로 빠르게 코엑스를 입력했을 때, 아래와 같이 API 콜이 한번만 이루어진 것을 확인할 수 있습니다.

 

 

Hook

debounce를 다른 곳에서 재사용할 수 있겠다고 생각했습니다.

그래서 위의 작업을 hook으로 만들어 사용하고자 했고 구글에 useDebounce라고 검색하니 많은 레퍼런스를 확인할 수 있었습니다!

 

useDebounce

import { useEffect, useState } from "react";

function useDebounce(value: any = null, delay: number = 200) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value]);
  return debouncedValue;
}

export default useDebounce;

 

const [searchAddress, setSearchAddress] = useState("");
const debouncedAddress = useDebounce(searchAddress, 200);

function handleChangeSearch(e: React.ChangeEvent<HTMLInputElement>) {
  const {
    target: { value },
  } = e;

  setSearchAddress(value);
}

async function handleSearchAddress(searchAddress: string) {
  const data = await kakaoLocalSearchApi(searchAddress);

  setLocationList(data);
  handleAddressList(true);
}

useEffect(() => {
  if (debouncedAddress.length > 0) {
    handleSearchAddress(debouncedAddress);
  }
}, [debouncedAddress]);

 

위와 같이 커스텀한 useDebounce를 사용할 수 있습니다 :)

 

ref: https://usehooks.com/useDebounce/

 

 


+

 

개발을 진행하다가 어떤 부분에서 광클을 하면 에러가 발생하는 문제가 발생했습니다. (three.js 렌더링쪽...)

 

어떻게 해결해야할지 고민하다가 onClick 함수를 debounce로 감싸기로 했습니다.

그래서 추가적으로 useDebouncedCallback이라는 Hook을 만들었습니다.

 

ref.current값이 리렌더링되어도 바뀌지 않는 특성을 이용하여 아래와 같이 작성하여 사용할 수 있었습니다.

그리고 useCallback을 이용하여 똑같은 콜백함수를 실행시키는 것을 기억하고 불필요하게 업데이트하는 것을 방지합니다.

import { useCallback, useRef } from "react";

function useDebouncedCallback(callback: Function, delay: number) {
  const timeout = useRef<any>();
  return useCallback(
    (...args) => {
      const later = () => {
        clearTimeout(timeout.current);
        callback(...args);
      };

      clearTimeout(timeout.current);
      timeout.current = setTimeout(later, delay);
    },
    [callback, delay],
  );
}

export default useDebouncedCallback;

+

깃허브에 아래와 같이 잘 구현된 hook 라이브러리가 있네요!

 

https://github.com/xnimorz/use-debounce

 

GitHub - xnimorz/use-debounce: A debounce hook for react

A debounce hook for react. Contribute to xnimorz/use-debounce development by creating an account on GitHub.

github.com

 

댓글