[Today I Learned] throttling 과 Debouncing

suwoncityboyyy·2023년 2월 1일
0

짧은 시간 간격으로 연속해서 이벤트가 발생했을때 과도한 이벤트 호출을 방지하기 위해 쓰이는 기법들인 Throttling & Debouncing 에 대해 알아보자.

그전에 Lodash를 알고 넘어가자.

Lodash

일반적인 유틸 함수들을 모아서 제공해주는 라이브러리 이다.
debounce나 throttle 처리를 할 때 lodash에서 제공하는 함수를 이용한다.
이 외에 데이터타입 등을 순회하면서 각 요소마다 원하는 기능을 실행할 때에도 사용한다.
설치법은 간단하다.

npm install --save lodash

throttling

짧은 시간 간격으로 연속해서 발생한 이벤트들을 일정시간 딜레이를 줘서 호줄되지 않게 해주는 작업이다.
주로 스크롤 이벤트에서 많이 쓰임.

Debouncing

연속으로 호출되는 함수들 중에 마지막에 호출되는 함수(또는 제일 처음 함수)만 실행되도록 하는 작업이다.

주로 실시간 입력값, 화면 resize 이벤트에서 쓰임.

lodash 라이브러리로 .debounce , .throttle 사용 예시

import React, { useCallback, useEffect, useState } from "react";
import _ from "lodash";

type ControlDelay = (callback: (...args: any[]) => void, delay: number) => any;

export default function Company() {
  const [selected, setSelected] = useState("customThrottle"); // customThrottle, customDebounce, lodashThrottle, lodashDebounce
  const [searchText, setSearchText] = useState("");
  const [inputText, setInputText] = useState("");
  console.log("searchText:", searchText);

  const throttle: ControlDelay = (callback, delay) => {
    let timerId: NodeJS.Timeout | null = null;
    let latestArgs: any[] = [];
    return (...args: any[]) => {
      // For trailing edge
      latestArgs = args;
      if (timerId) return;
      // For Leading edge
      callback(...args);
      timerId = setTimeout(() => {
        if (!_.isEqual(latestArgs, args)) callback(...latestArgs);
        timerId = null;
      }, delay);
    };
  };

  const debounce: ControlDelay = (callback, delay) => {
    let timerId: NodeJS.Timeout | null = null;
    return (...args: any[]) => {
      if (timerId) clearTimeout(timerId);
      timerId = setTimeout(() => {
        callback(...args);
      }, delay);
    };
  };

  const selectEventControl = (delay: number) => {
    switch (selected) {
      case "customThrottle":
        return throttle((text) => setSearchText(text), delay);
      case "customDebounce":
        return debounce((text) => setSearchText(text), delay);
      case "lodashThrottle":
        // _.throttle 의 기본 옵션은 leading & trailing edge
        return _.throttle((text) => setSearchText(text), delay, {
          leading: true,
          trailing: true,
        });
      case "lodashDebounce":
        // _.debounce 의 기본 옵션은 trailing edge
        return _.debounce((text) => setSearchText(text), delay, {
          leading: false,
          trailing: true,
        });
      default:
        break;
    }
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const handleSearchText = useCallback(selectEventControl(2000), [selected]);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    handleSearchText(e.target.value);
    setInputText(e.target.value);
  };

  // resize 이벤트 핸들러
  const handleResize = _.debounce(() => console.log("resize 완료"), 1000);

  useEffect(() => {
    window.addEventListener("resize", handleResize);

    return () => {
      handleResize.cancel(); // 현재 동작중인 스케쥴 동작 취소. 메모리 누수 방지.
      window.removeEventListener("resize", handleResize); // resize 이벤트리스너 제거
    };
  }, [handleResize]);

  return (
    <div style={{ paddingLeft: 20, paddingRight: 20 }}>
      <h1>Input Text 및 Resize 이벤트 예제</h1>
      <h2>원하는 Throttle, Debounce 함수를 선택하세요</h2>
      <label style={{ display: "block" }}>
        <input
          checked={selected === "customThrottle"}
          onChange={() => setSelected("customThrottle")}
          type={"radio"}
        />
        customThrottle
      </label>
      <label style={{ display: "block" }}>
        <input
          checked={selected === "customDebounce"}
          onChange={() => setSelected("customDebounce")}
          type={"radio"}
        />
        customDebounce
      </label>
      <label style={{ display: "block" }}>
        <input
          checked={selected === "lodashThrottle"}
          onChange={() => setSelected("lodashThrottle")}
          type={"radio"}
        />
        lodashThrottle
      </label>
      <label style={{ display: "block" }}>
        <input
          checked={selected === "lodashDebounce"}
          onChange={() => setSelected("lodashDebounce")}
          type={"radio"}
        />
        lodashDebounce
      </label>

      <br />
      <input
        placeholder="입력값을 넣고 쓰로틀링/디바운싱 테스트 해보세요"
        style={{ width: "100%" }}
        onChange={handleChange}
        type={"text"}
      />
      <p>Search Text: {searchText}</p>
      <p>Input Text: {inputText}</p>
    </div>
  );
}

profile
주니어 개발자 기술노트

0개의 댓글