티스토리 뷰

Debounce와 Throttle

`Debounce`와 `Throttle`은 이벤트 핸들러를 효율적으로 관리하기 위한 기술이다. 이 두 가지는 비슷하지만, 사용 목적과 동작 방식에서 중요한 차이점이 존재한다.

 

Debounce

연속적으로 발생하는 이벤트를 일정 시간동안 지연시키고, 이벤트가 발생하지 않을 때, 마지막 한 번만 실행하는 기법이다. 주로 사용자가 입력을 빠르게 할 때, 마지막 입력만 처리하고 싶을 때 유용하다.

 

동작 방식은 다음과 같다.

  1. 이벤트가 발생할 때마다 지정된 시간(대기 시간) 동안 대기
  2. 대기 시간동안 추가적인 이벤트가 발생하지 않으면 마지막 이벤트 처리
  3. 대기 시간동안 추가 이벤트가 발생하면 대기 시간을 초기화하고 다시 대기

 

아래의 예시 코드를 보고 조금 더 이해해보자.

입력 필드에서 검색어를 입력할 때마다 API 요청을 방지하기 위해 `Debouncing`을 사용할 수 있다.

function debounce(func, wait) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
}

function handleSearch(event) {
    console.log('Search query:', event.target.value);
}

const debouncedSearch = debounce(handleSearch, 300);

document.getElementById('searchInput').addEventListener('input', debouncedSearch);

 

사용자가 입력 필드에 글을 입력하면 `handleSearch` 함수가 300ms 후에 실행된다. 입력이 계속되면 대기 시간이 초기화되기 때문에, 마지막 입력만 처리된다.

 

React 예제 코드

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

function debounce(func, wait) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
}

function SearchComponent() {
    const [query, setQuery] = useState('');

    const handleSearch = useCallback((event) => {
        console.log('Search query:', event.target.value);
    }, []);

    const debouncedSearch = useCallback(debounce(handleSearch, 300), [handleSearch]);

    const handleChange = (event) => {
        setQuery(event.target.value);
        debouncedSearch(event);
    };

    return (
        <input
            type="text"
            value={query}
            onChange={handleChange}
            id="searchInput"
            placeholder="Search..."
        />
    );
}

export default SearchComponent;

 

`Debounce`는 입력 이벤트를 일정 시간동안 지연시켜 과도한 호출을 방지하는 데 유용하니, 적절한 때에 고려해 볼 필요가 있다.

 

Throttle

이벤트가 일정 시간 간격으로만 발생하도록 제한하는 기법이다. 주로 스크롤이나 리사이즈와 같은 빈번하게 발생하는 이벤트를 제어할 때 유용하다.

 

동작 방식은 아래와 같다.

  1. 이벤트가 발생할 때마다 지정된 시간 간격(제한 시간) 동안 함수 호출 제한
  2. 제한 시간이 지나면 함수가 호출되고, 이후 같은 시간 간격으로만 함수 호출

 

예제 코드를 보고 이해해보자.

스크롤 이벤트가 빈번하게 발생하는 것을 제어하기 위해 `Throttling`을 사용할 수 있다.

function throttle(func, limit) {
    let lastFunc;
    let lastRan;
    return function(...args) {
        const now = Date.now();
        if (!lastRan) {
            func.apply(this, args);
            lastRan = now;
        } else {
            clearTimeout(lastFunc);
            lastFunc = setTimeout(() => {
                if (now - lastRan >= limit) {
                    func.apply(this, args);
                    lastRan = now;
                }
            }, limit - (now - lastRan));
        }
    };
}

function handleScroll() {
    console.log('Scroll event');
}

const throttledScroll = throttle(handleScroll, 1000);

window.addEventListener('scroll', throttledScroll);

 

위 코드를 설명하자면, 사용자가 스크롤을 할 때마다 `handleScroll` 함수는 1초(1,000ms) 간격으로 호출된다. 이로 인해 빈번하게 발생하는 함수 호출을 방지할 수 있다.

 

React 예제 코드

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

function throttle(func, limit) {
    let lastFunc;
    let lastRan;
    return function(...args) {
        const now = Date.now();
        if (!lastRan) {
            func.apply(this, args);
            lastRan = now;
        } else {
            clearTimeout(lastFunc);
            lastFunc = setTimeout(() => {
                if (now - lastRan >= limit) {
                    func.apply(this, args);
                    lastRan = now;
                }
            }, limit - (now - lastRan));
        }
    };
}

function ScrollComponent() {
    const [scrolling, setScrolling] = useState(false);

    const handleScroll = useCallback(() => {
        console.log('Scroll event');
        setScrolling(true);
    }, []);

    const throttledScroll = useCallback(throttle(handleScroll, 1000), [handleScroll]);

    useEffect(() => {
        window.addEventListener('scroll', throttledScroll);
        return () => {
            window.removeEventListener('scroll', throttledScroll);
        };
    }, [throttledScroll]);

    return (
        <div>
            <p>Scroll to see the effect in the console</p>
            <div style={{ height: '2000px' }}></div>
            {scrolling && <p>Scrolling...</p>}
        </div>
    );
}

export default ScrollComponent;

 

`Throttle`는 이벤트 호출을 제한하여 성능을 개선할 수 있다. `useState`와 `useCallback`을 사용해 이 기능을 구현할 수 있다.

 

특성 Debounce Throttle
동작 원리 일정 시간동안 이벤트가 없을 때 마지막 한 번만 실행 지정된 시간 간격으로만 함수 호출 허용
주 사용 사례 입력 필드의 자동 검색, 창 크기 조정 등 스크롤, 윈도우 크기 조정 등
성능 자주 발생하는 이벤트를 최적화하여 불필요한 호출 방지 일정 간격으로 이벤트 처리하여 호출 빈도 조절

 

아래의 그림으로 다시 한 번 복습해보자.

 

Debounce 동작 원리

+---------+         +---------+         +---------+
|   A     |         |   B     |         |   C     |       =>  마지막 A, B, C 이후 처리
+---------+         +---------+         +---------+
      ^                  ^                   ^
      |                  |                   |
    타이머 초기화       타이머 초기화        타이머 초기화
      |                  |                   |
      +---- 300ms ---->  마지막 이벤트 처리

 

Throttle 동작 원리

+---------+    +---------+    +---------+    +---------+    +---------+
|   A     |    |   B     |    |   C     |    |   D     |    |   E     |   =>  1초마다 호출
+---------+    +---------+    +---------+    +---------+    +---------+
      ^              ^              ^              ^              ^
      |              |              |              |              |
      +---- 1초 ---->+---- 1초 ---->+---- 1초 ---->+---- 1초 ---->+---- 1초

 

이러한 기법들을 적절히 활용하면 웹 애플리케이션의 성능과 사용자 경험을 크게 향상시킬 수 있다.