Debounce 함수 사용하여 중복 요청 및 딜레이 문제 해결하기

Wynter24·2023년 10월 19일
0

구현하고자 하는 기능: 연관검색어

  • 연관검색어 리스트 Api 연결하여 get하기
const getSearch = async () => {
    try {
      const response = await getAPI<SearchResult[]>(`/api/quiz/search-bar?keyword=${searchInput}`)
      setRelativeSearch(response.data)
      console.log("퀴즈 검색",response.data);
    } catch (error) {
      console.log('error', error);
    }
  }
  • input의 상태변화 따라 get 요청
    input에 상태변화 즉 값이 입력될 때마다 get 요청을 보냈다.
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSearchInput(event.target.value);
    getSearch();
  };
return(
  <div className="absolute top-full left-0 right-0 bg-white border border-t-0 z-10">
        {searchInput &&
          relativeSearch.map((result) => ( // searchResults 결과
            <div key={result.id} className="p-2 hover:bg-gray-100" onClick={()=>navigate('')}>
              {result.title}
            </div>
          ))}
      </div>
  )

중복 요청 및 딜레이 문제 발생

검색어를 입력할 때마다 get요청을 보내고 있다

해결방안 모색

  1. 검색어 입력 시 딜레이 적용하기
    : 이 방법은 가장 간단한 방법으로, 입력이 발생할 때마다 일정 시간(예: 300ms) 딜레이 후에 API 요청을 보낸다. 이 방법은 구현이 간단하고 빠르게 동작하지만, 사용자 경험에 영향을 줄 수 있다. 사용자가 입력을 중단했을 때도 딜레이가 발생하기 때문이다. (비추천)

  2. Debounce 함수 사용하기
    : 디바운스 함수를 사용하면 사용자가 입력을 계속할 때는 API 요청을 보내지 않고, 입력이 일정 시간 동안 멈추었을 때만 요청을 보낸다. 이 방법은 사용자 경험을 개선할 수 있으며, 중복 요청을 방지한다. 구현이 조금 복잡할 수 있지만, 좋은 선택이다.

  3. API 요청 취소 기능 추가
    : 이 방법은 Axios와 같은 HTTP 라이브러리에서 제공하는 요청 취소 기능을 사용하여 중복 요청을 방지하고, 사용자가 새로운 입력을 시작할 때 이전 요청을 취소한다. 하지만 이 방법은 주로 다른 상황에서 HTTP 요청을 관리하고 취소하는 데 사용된다. (사용 목적 불일치)

  • HTTP 요청을 관리하고 취소해야 하는 주요 상황은 다음과 같다

    	1. 사용자 인터페이스 관련 상황 : 사용자가 페이지를 떠나거나 다른 작업을 수행할 때 이전 요청을 취소하고 새로운 요청을 보낼 때.
    	2. 연속적인 요청 관리 : 사용자가 반복적으로 동일한 요청을 보낼 때 이전 요청을 취소하고 최신 요청만 처리할 때.
    	3. 빠른 응답 필요 : 빠른 응답이 필요한 상황에서 이전 요청을 취소하고 새로운 요청을 보낼 때.
    	4. 네트워크 문제 처리 : 네트워크 연결이 불안정한 경우 요청을 취소하고 사용자에게 알리는 경우.
    	5. 자원 관리 : 리소스 효율성과 메모리 누수 방지를 위해 불필요한 HTTP 요청을 취소할 때.

Debounce 훅 만들어서 사용하기

Debounce 함수는 과도한 요청, 처리를 수행하게 될 경우 발생할 수 있는 성능 저하를 막기 위해 이를 효과적으로 제어하여 성능을 개선하는 방법 중 하나이다.
이 함수는 연속적으로 호출되는 함수 중 마지막 함수만 호출한다.
즉 입력이 멈춘 후에만 함수를 실행한다.

// useDebounce.ts
import { useEffect, useState } from 'react';

const useDebounce = (value: string, delay: number) => {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    }; //value 변경 시점에 clearTimeout
  }, [value]);

  return debouncedValue;
};

export default useDebounce;
// SearchBar.tsx
import { useEffect, useState } from 'react';
import { getAPI } from '@/apis/axios';
import useDebounce from '@/hooks/useDebounce';
import { useNavigate } from 'react-router';

const SearchBar = () => {
  type SearchResult = { 
    title: string;
    id: number;
  };

  const [searchInput, setSearchInput] = useState('');
  const [relativeSearch,setRelativeSearch] = useState<SearchResult[]>([]);
  const debouncedSearchTerm = useDebounce(searchInput, 200);
  const navigate = useNavigate();

  useEffect(() => {
    if(debouncedSearchTerm) {
      getSearchResult();
    } else {
      setRelativeSearch([]);
    }
  },[debouncedSearchTerm]);

  const getSearchResult = async () => {
    try {
      const response = await getAPI<SearchResult[]>(`/api/quiz/search-bar?keyword=${searchInput}`)
      setRelativeSearch(response.data)
      console.log("퀴즈 검색",response.data);
    } catch (error) {
      console.log('error', error);
    }
  }

  const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSearchInput(event.target.value);
  };

  return (
    <div className="relative">
      <form>   
         <div className="relative">
            <input type="search" id="default-search" 
              className="block w-full m-0 h-[37px] p-4 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" 
              placeholder="Search" required 
              onChange={handleSearchChange}
            />
            <button className="absolute inset-y-0 right-0 flex items-center pr-3 ">
              <svg className="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
                <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
              </svg>
            </button>
          </div>
      </form>

      <div className="absolute top-full left-0 right-0 bg-white border border-t-0 z-10">
        {searchInput &&
          relativeSearch.map((result) => ( // searchResults 결과
            <div key={result.id} className="p-2 hover:bg-gray-100 cursor-pointer" onClick={()=>navigate('')}> {/*퀴즈 상세페이지로 이동*/}
              {result.title}
            </div>
          ))}
      </div>
    </div>
  );
};

export default SearchBar;

Debounce와 Throttle의 차이점


위의 개선 사항을 적용하먼 검색어를 입력할 때 빠르게 API 요청을 보내고, 중복 요청을 방지하며, 딜레이를 적용하여 부드러운 사용자 경험을 제공할 수 있다!


참고자료
[React] 검색에서 Debounce 적용해보기

profile
내가 다시 보려고 쓰는 개발.log

0개의 댓글