:ledger: Throttling과 Debouncing을 이용한 버튼 이벤트 처리를 구현해보자.

웹 개발에서 자주 사용되는 두 가지 개념인 ThrottlingDebouncing을 간단히 구현해보고, 이를 통해 API 요청을 제어하는 방법을 살펴보자.

:one: Throttling이란?

Throttling은 특정 시간 간격 동안에 한 번만 함수를 호출하도록 제한하는 방법이다. 사용자가 여러 번 빠르게 클릭하거나 스크롤할 때, 불필요한 이벤트 처리를 방지할 수 있음.

  • 특정 시간 간격동안 한 번만 함수를 호출하도록 제한하는 방법
  • 연속해서 발생하는 이벤트들을 일정시간 단위(delay)로 그룹화하여 처음 또는 마지막 이벤트 핸들러만 호출되도록 하는 것을 말하며 주로 무한스크롤에서 사용한다.
타입 설명 예시
Leading Edge 이벤트가 처음 발생할 때 핸들러가 실행됨. 이후 주어진 시간 동안은 이벤트가 무시됨. 사용자가 스크롤을 시작할 때 처음에만 API 호출이 이루어지고, 일정 시간 동안 추가 호출이 무시됩니다.
Trailing Edge 이벤트가 반복적으로 실행될 때, 주어진 시간(delay)이 지나면 마지막 이벤트를 처리 Leading Edge와 비슷하지만 주어진 시간의 마지막 이벤트에 API 호출이 이루어짐
Leading & Trailing Edge 주어진 시간에 대해 이벤트가 처음 발생할 때 핸들러가 실행되고, 주어진 시간이 지나면 마지막 이벤트도 처리 사용자가 버튼을 여러 번 클릭할 때 처음 클릭 시 바로 API 호출이 이루어지고, 주어진 시간의 마지막 이벤트에도 API 호출이 이루어짐

:two: Debouncing이란?

Debouncing은 사용자가 연속적으로 이벤트를 발생시킬 때, 마지막 이벤트가 발생하고 일정 시간이 지난 후에만 함수를 호출하는 방식이다.

  • 연속해서 이벤트가 발생하면 이벤트 핸들러를 호출하지 않다가 마지막 이벤트로부터 일정 시간(delay)이 경과한 후에 한 번만 호출하도록 하는 것을 말하며 주로 입력값 실시간 검색, 화면 resize 이벤트 등에서 사용된다.

:three: 코드로 알아보자!

:pushpin: 3-1) 예시 코드

import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

const Home = () => {
  const navigate = useNavigate();
  let timerId = null;

  // Throttling 함수
  const throttle = (delay) => {
    if (timerId) {
      // 이미 setTimeout이 진행 중이면 return
      return;
    }
    console.log(`API 요청 실행! ${delay}ms 동안 추가 요청 안 받음~`);
    timerId = setTimeout(() => {
      console.log(`${delay}ms 지남. 추가 요청 받음!`);
      timerId = null;
    }, delay);
  };

  // Debouncing 함수
  const debounce = (delay) => {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 API 요청 실행`);
      timerId = null;
    }, delay);
  };

  // 페이지 이동 함수
  const handleMove = () => {
    navigate("/company");
  };

  // 컴포넌트가 언마운트될 때 타이머 정리
  useEffect(() => {
    return () => {
      if (timerId) clearTimeout(timerId);
    };
  }, []);

  return (
    <div>
      <h2>Button 이벤트 예제</h2>
      <button onClick={() => throttle(2000)}>쓰로틀링 버튼</button>
      <button onClick={() => debounce(2000)}>디바운싱 버튼</button>
      <div>
        <button onClick={handleMove}>페이지 이동</button>
      </div>
    </div>
  );
};

export default Home;

:pushpin: 3-2) Throttle 함수 설명

이 함수는 설정한 delay동안 추가적인 요청을 무시하고, setTimeout을 이용해 요청 주기를 제어함

const throttle = (delay) => {
  if (timerId) {
    // 이미 타이머가 존재하면 새로운 요청을 무시
    return;
  }
  console.log(`API 요청 실행! ${delay}ms 동안 추가 요청 안 받음~`);
  timerId = setTimeout(() => {
    console.log(`${delay}ms 지남. 추가 요청 받음!`);
    timerId = null;
  }, delay);
};

:pushpin: 3-2) Debounce 함수 설명

이 함수는 사용자가 이벤트를 발생시킬 때마다 타이머를 리셋하여 마지막 이벤트가 발생한 후 일정 시간이 지나야만 함수가 호출되도록 한다.

const debounce = (delay) => {
  if (timerId) {
    clearTimeout(timerId);
  }
  timerId = setTimeout(() => {
    console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 API 요청 실행`);
    timerId = null;
  }, delay);
};

:pushpin: 3-3) 메모리 누수 방지

타이머가 걸린 상태에서 컴포넌트가 엄마운트될 경우, 메모리 누수를 방지하기 위해 타이머를 정리하는 역할을 한다.

useEffect(() => {
  return () => {
    if (timerId) clearTimeout(timerId);
  };
}, []);

:four: Lodash 라이브러리 사용

Lodashthrottlingdebouncing 기법을 보다 쉽게 사용할 수 있도록 도와주는 유용한 라이브러리다.

:pushpin: 4-1) Lodash 설치

# 터미널 입력
yarn add lodash

:pushpin: 4-2) Lodash 사용

설치 후 아래와 같이 import해서 사용이 가능하다.

import _ from "lodash";

:pushpin: 4-3) Lodash로 리팩토링

위에서 사용한 throttle, debounce함수를 lodash를 사용하여 변경해주었다.

// throttle
const throttle = _.throttle(() => {
  console.log("API 요청 실행! 2000ms 동안 추가요청 안받음!");
}, 2000);

// debounce
const debounce = _.debounce(() => {
  console.log("마지막 요청으로부터 2000ms 지났으므로 API 요청 실행!");
}, 2000);

:pushpin: 4-4) 검색창 디바운싱

입력값이 변경될 때마다 마지막 입력 후 2초가 지나면 상태(searchText)가 업데이트된다. 즉, 사용자가 입력을 빠르게 여러번 할 때 API 요청이 불필요하게 여러 번 발생하는 것을 방지한다.

const [searchText, setSearchText] = useState("");
const [inputText, setInputText] = useState("");  

// 입력값에 debounce 적용
const handleSearchText = useCallback(_.debounce((text) => setSearchText(text), 2000), []);


const handleChange = (e) => {
  setInputText(e.target.value);
  handleSearchText(e.target.value);
};  

// return 
<h2>디바운싱 예제</h2>
<input
  placeholder="입력값을 넣고 디바운싱 테스트를 해보세요."
  style=
  onChange={handleChange}
  type="text"
/>
<p>Search Text: {searchText}</p>
<p>Input Text: {inputText}</p>

:fire: 마무리

ThrottlingDebouncing은 이벤트의 과도한 호출을 방지하고 성능을 최적하하는데에 유용하다. 그리고 이를 더 쉽게 사용할 수 있도록 도와주는 라이브러리인 lodash를 알아보고 예제를 만들어 보며 이해할 수 있었다.

연습 코드

throttling-debouncing-lodash-study - rarrit github