Throttling & Debouncing 쓰로틀링과 디바운싱

suno·2023년 2월 1일
3
post-thumbnail

Throttling과 Debouncing이란?

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

자바스크립트에서는 Timer Web API인 setTimeout 메소드를 사용해서 Throttlint과 Debouncing을 구현한다.


Throttling

짧은 시간 동안 연속해서 발생한 이벤트들을 일정 시간 단위(delay)로 그룹화하여, 처음 또는 마지막 이벤트 핸들러만 호출하도록 하는 것

예시: 무한스크롤


Debouncing

짧은 시간 동안 연속해서 발생한 이벤트를 호출하지 않다가, 마지막 이벤트로부터 일정 시간(delay) 이후에 한번만 호출하도록 하는 것

예시: 입력값 실시간 검색, 화면 resize 이벤트


메모리 누수 (Memory Leak)

메모리 누수란, 필요하지 않은 메모리를 계속 점유하고 있는 현상을 말한다.

Q. setTimeOut이 메모리 누수를 유발할까?

상황에 따라 다르다.

  • 페이지 이동 없이 setTimeout을 실행하고 타이머 함수가 종료될 때까지 기다린다면 메모리 누수는 없다.
  • 리액트로 만든 SPA 웹사이트에서 setTimeout이 동작중일 때 페이지를 이동하면, 컴포넌트는 언마운트 되지만 타이머는 여전히 메모리를 차지한 채 동작한다. 이 경우는 메모리 누수에 해당한다.

코드

import { useCallback, useEffect, useState } from 'react';

type ControlDelay = (callback: () => void, delay: number) => void;

export default function Search() {
  const [checked, setChecked] = useState<string>('throttling search');

  const [text, setText] = useState<string>('');
  const [result, setResult] = useState<string>('');

  const delay = 1000;
  let timer: NodeJS.Timeout | null = null;

  const throttling: ControlDelay = (callback, delay) => {
    if (timer) return;

    timer = setTimeout(() => {
      callback();
      console.log('throttling 종료.');
      timer = null;
    }, delay);
  };

  const debouncing: ControlDelay = (callback, delay) => {
    if (timer) clearTimeout(timer);

    timer = setTimeout(() => {
      callback();
      console.log('debouncing 종료.');
      timer = null;
    }, delay);
  };

  const handleCheckboxChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      if (checked === 'throttling search') {
        setText(e.target.value);
        throttling(() => setResult(e.target.value), delay);
        return;
      }
      if (checked === 'debouncing search') {
        setText(e.target.value);
        debouncing(() => setResult(e.target.value), delay);
        return;
      }
    },
    [checked]
  );

  const handleResize = useCallback(() => {
    if (checked === 'throttling resize') {
      throttling(() => console.log('throttling resize'), delay);
      return;
    }
    if (checked === 'debouncing resize') {
      debouncing(() => console.log('debouncing resize'), delay);
      return;
    }
  }, [checked]);

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

    // cleanup 함수로 컴포넌트가 언마운트 될 때 이벤트 리스너를 제거한다.
    // 제거하지 않으면 timer가 계속 동작해 메모리 누수가 발생한다.
    return () => {
      window.removeEventListener('resize', handleResize);
      if (timer) clearTimeout(timer);
    };
  }, [handleResize, timer]);

  return (
    <Container>
      <Button onClick={() => navigate(-1)}>뒤로가기</Button>
      {[
        'throttling search',
        'debouncing search',
        'throttling resize',
        'debouncing resize',
      ].map((name) => (
        <label key={name}>
          <input
            type='radio'
            name={name}
            onChange={() => setChecked(name)}
            checked={checked === name}
          />
          {name}
        </label>
      ))}
      <Input type='text' onChange={handleCheckboxChange} />
      <p>일반: {text}</p>
      <p>result: {result}</p>
    </Container>
  );
}

Lodash 사용

lodash는 성능이 보장되어 있는 다양한 메소드를 제공하는 JavaScript 라이브러리이다.

array, collection, date 등 데이터 구조 뿐만 아니라, throttledebounce 메소드도 제공한다.


💡 느낀점

프로젝트에서 form 제출 이벤트를 처리할 때 Throttling과 Debouncing에 대해 깊게 고민해본 적이 없었던 것 같다.
이벤트 핸들러 연속 호출 방지를 할 때는 단순히 setTimeout으로 button을 disabled 했다.

이제는 이러한 개념에 대해 알았으니 다양한 로직에 적용해볼 수 있겠다.

Throttling

  • form 제출 핸들러
  • 무한스크롤 API fetch

Debouncing

  • 실시간 검색 시 query 요청
  • 화면 resize 이벤트 핸들러
  • 지도 API 이동/확대 이벤트 핸들러
profile
Software Engineer 🍊

0개의 댓글