[TIL] Throttling & Debouncing

·2023년 12월 13일
1

TIL

목록 보기
55/85
post-thumbnail

Throttling & Debouncing이란?

디바운싱과 쓰로틀링 모두 UI에서 발생하는 이벤트를 제어하는 방법이다.
과도하게 이벤트 처리 함수(콜백함수)가 호출되지 않도록 하여 부하를 방지하기 위해 쓰이는 방법이다.

👉 둘 다 짧은 시간 간격으로 연속해서 이벤트가 발생했을 때 과도한 이벤트 핸들러 호출을 방지하는 기법

쓰로틀링 (Throttling)

  • 연속해서 이벤트가 발생할 때, 첫 이벤트 처리 후 일정 시간 동안 나머지 이벤트를 무시하는 방식
  • 주로 사용되는 예 : 무한 스크롤

[type 1: Leading Edge]

[type 2: Trailing Edge]

예시

const Home = () => {
 
  // 생략

  let timerId = null;

  const throttle = (delay) => {
    if (timerId) {
      // timerId가 있으면 바로 함수 종료
      return;
    }
    console.log(`API 요청 실행! ${delay}ms 동안 추가요청은 안받습니다!`);
    timerId = setTimeout(() => {
      console.log(`${delay}ms 지남 추가 요청 받습니다!`);
      timerId = null; // 2초 지나면 timerId가 null 이 되어 함수 실행 가능해짐
    }, delay);
  };
  
 // 생략
  
  return (
    <StDiv>
      <h1>Button 이벤트 예제</h1>
      <button onClick={() => throttle(2000)}>쓰로틀링 버튼</button>
      <button onClick={() => debounce(2000)}>디바운싱 버튼</button>
      <div>
        <button onClick={() => navigate("/company")}>페이지 이동</button>
      </div>
    </StDiv>
  );
};

export default Home;

메모리 누수 (Memory Leak)

  • 메모리 누수(Memory Leak) 란? : 필요하지 않은 메모리를 계속 점유하고 있는 현상

Q. setTimeout이 메모리 누수를 유발하는가?
A. 하나의 페이지에서 페이지 이동 없이 setTimeout을 동작시키고, 타이머 함수가 종료될 때까지 기다린다면 메모리 누수는 없다. 그런데 페이지 이동 전에 setTimeout으로 인해 타이머가 동작중인 상태에서 clearTimeout 을 해주지 않고 페이지를 이동하면 컴포넌트는 언마운트 되었음에도 불구하고 타이머는 여전히 메모리를 차지하고 동작하고 있다. 이 경우 메모리 누수가 발생했다고 말할 수 있다. (cf. 리액트로 만든 SPA 웹사이트는 페이지 이동 시 컴포넌트가 언마운트 된다.)

다음과 같이 클린업을 통해 컴포넌트가 언마운트 될 때 clearTimeout을 실행한다면, 메모리 누수가 발생하지 않는다.

const Home = () => {
  const [state, setState] = useState(true);
  const navigate = useNavigate();
  //메모리 누수 방지 ⭐️
    useEffect(() => {
      return () => {
        // 홈 컴포넌트 언마운트 될 때!
        if (timerId) clearTimeout(timerId);
      };
    });

  let timerId = null;

  const throttle = (delay) => {
    if (timerId) {
      // timerId가 있으면 바로 함수 종료
      return;
    }
    console.log(`API 요청 실행! ${delay}ms 동안 추가요청은 안받습니다!`);
    timerId = setTimeout(() => {
      console.log(`${delay}ms 지남 추가 요청 받습니다!`);
      timerId = null; // 2초 지나면 timerId가 null 이 되어 함수 실행 가능해짐
    }, delay);
  };
  
  // 생략

디바운싱 (Debouncing)

  • 연속해서 이벤트가 발생할 때 전체 이벤트를 하나의 그룹으로 묶어서 '처음 or 마지막' 하나만 처리
  • 주로 사용되는 예 : 검색어 자동완성, 화면 resize 이벤트

예시

const Home = () => {
 // 생략
  let timerId = null;

  // 반복적인 이벤트 이후 delay 가 지나면 함수 실행
  const debounce = (delay) => {
    if (timerId) {
      // 할당되어있는 timerId에 해당하는 타이머 제거
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 API 요청 실행`);
      timerId = null;
    }, delay);
  };
  return (
    <StDiv>
      <h1>Button 이벤트 예제</h1>
      <button onClick={() => throttle(2000)}>쓰로틀링 버튼</button>
      <button onClick={() => debounce(2000)}>디바운싱 버튼</button>
      <div>
        <button onClick={() => navigate("/company")}>페이지 이동</button>
      </div>
    </StDiv>
  );
};

export default Home;

lodash

setTimeout을 통해 쓰로틀링과 디바운싱을 구현할 수 있지만, lodash 라는 라이브러리를 통해 더 쉽게 구현할 수 있다.

설치

yarn add lodash

예시

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

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

  // useCallback을 써야 정상 동작 한다. 메모이제이션을 해야 함.
  const handleSearchText = useCallback(
    _.debounce((text) => {
      setSearchText(text);
    }, 2000),
    []
  ); // 2초 뒤에 실행

  const handleChange = (e) => {
    setInputText(e.target.value);
    handleSearchText(e.target.value);
  };
  return (
    <div>
      <h1>디바운싱 예제</h1>
      <input
        placeholder="입력값을 넣고 디바운싱 테스트를 해보세요"
        type="text"
        onChange={handleChange}
      />
      <p>Search Text : {searchText}</p>
      <p>input Text : {inputText}</p>
    </div>
  );
}

export default App;

profile
느리더라도 조금씩, 꾸준히

0개의 댓글