[React] 검색에서 Debounce 적용해보기

sjoleee·2022년 9월 16일
32
post-thumbnail

Debounce란?

과도한 요청, 처리를 수행하게 될 경우 발생할 수 있는 성능 저하를 막기 위해 이를 효과적으로 제어하여 성능을 개선하는 방법 중 하나!

어떤 식으로 제어하느냐? 연속적으로 호출되는 함수 중 마지막 함수만 호출한다.

예를 들어, 검색기능이라고 생각해보자.
input에 "sjoleee"을 검색하려고 타이핑을 한다.
debounce 설정을 하지 않을 경우에는

"s"검색 👉 "sj"검색 👉 "sjo"검색 👉 "sjol"검색 👉 ...

이렇게 한 글자를 타이핑할때마다 검색하게 된다.

반면, debounce를 설정(타이머 1초)할 경우,
"s"를 타이핑하고 1초 이내에 "j"를 입력할 경우, 1초를 세던 타이머는 "j"가 입력됨과 동시에 초기화되어 다시 1초를 세며, 검색은 타이머가 종료된 이후에 수행되므로 타이핑하고 1초 이내에 다음 알파벳을 타이핑할 경우, 검색이 되지 않는다.

"s"타이핑 👉 1초 이내에 "j"타이핑 👉 1초 이내에 "o"타이핑 👉 ...

이렇게 타이머가 끝나기 전에 계속 입력하면 결국 "sjoleee"에서 마지막 e를 입력하고 1초 이후에 단 1번 검색된다.

즉, 요청이 들어오고 일정 시간을 기다린 후 요청을 수행하며, 만약 일정 시간 안에 같은 요청이 추가로 들어오면 이전 요청은 취소된다

Throttle이란?

마찬가지로, 과도한 요청, 처리를 수행하게 될 경우 발생할 수 있는 성능 저하를 막기 위해 이를 효과적으로 제어하여 성능을 개선하는 방법 중 하나!

동작하는 방식이 debounce와 조금 다른데, 마지막 함수가 호출된 후 일정 시간이 지나기 전에는 다시 호출되지 않도록 한다.

예를 들어, 무한 스크롤을 구현한다고 생각해보자.
페이지의 하단(대충 하단이라고 표현하겠다.) 부근까지 스크롤이 내려가면 추가로 데이터를 불러와야해서 스크롤이 어디까지 내려갔는지를 감지해야하는 상황이다.
이때, throttle을 설정해두지 않는다면 스크롤이 내려가는 내내 스크롤 이벤트가 발생하여 성능에 문제를 일으킬 수 있다.
이를 일정 시간동안 한 번만 스크롤을 감지하도록 하여 과도한 이벤트를 제어한다.
왜 debounce를 쓰지 않고 throttle을 사용하냐면, debounce를 사용했다면 사용자가 페이지를 쭉~ 내려서 페이지 끝에 닿고 스크롤 이벤트가 더 이상 발생하지 않을 때가 되어서야 추가로 데이터를 불러오게 된다.
페이지 끝에 닿기 전에 추가로 데이터를 불러와서 무한한 스크롤을 만드는 것이 목적이라면 throttle이 더욱 적합하다.

둘의 차이점 요약

throttle은 정해진 시간에 한 번은 요청하게 된다. 반면 debounce는 정해진 시간으로 그룹화된 요청들 중 마지막 요청에 주목하는 것이다.
따라서 횟수를 조절해야할 뿐, 정기적으로 실행될 필요가 있는 것은 throttle로 처리하고 마지막 요청만 남겨도 되는 상황에서는 debounce가 더욱 효율적이라 할 수 있겠다.

프로젝트에서 적용

아이돌 프로젝트에서 검색기능을 구현할때 debounce를 적용했다.
useEffect를 사용한 customHook을 만들어서 사용했다.

//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;
//MemberCardList.tsx

import MemberCard from '../MemberCard';
import { MemberStore, SearchTextStore } from '@/store/store';
import useDebounce from '@/hooks/useDebounce';

const MemberCardList = () => {
  const { members } = MemberStore();
  const { searchText } = SearchTextStore();//input에 입력된 텍스트

  const debouncedSearchText = useDebounce(searchText, 200); //200ms로 설정된 debounce

  const filteredMembers = [...members].filter(
    (item) =>
      item.name.includes(debouncedSearchText) || item.groupName.includes(debouncedSearchText),
  );
//members 배열에서 name이나 groupName이 searchText와 같은 값을 가진 item을 찾는다.
//이때, searchText에 debounce를 적용한 debouncedSearchText를 활용한다.
  
  return (
    <div className="flex flex-wrap gap-3">
      {filteredMembers.map((item) => (
        <MemberCard key={item.id} {...item} />
      ))}
    </div>
  );
};

export default MemberCardList;

before

after

참고한 글
https://www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa
https://webclub.tistory.com/607

profile
상조의 개발일지

0개의 댓글