귀여운 눈높이에서 작성된, 🐰

하루 한 걸음씩, 성장 하는 중 입니다 🫶🏻

Studying/React

리액트 공부하기 - debounce, throttle (디바운스, 스로틀)

creamymood 2025. 5. 7. 14:49

오 생각보다 재밌잖아 . . ?

뭔가 실용적인 거라 그런가 .. ? 또욘님 . . 제가 공부해서 알려드릴게욤 기다료~ 🩷


scroll, input, mousemove 같은 이벤트는 짧은 시간 간격으로 연속해서 동작하는데

이러한 이벤트에 바인딩한 이벤트 핸들러는 과도하게 호출되어, 성능 저하와 관련된 문제를 일으킬 수 있다.

 

따라서 이렇게 짧은 간격으로 연속해서 발생하는 이벤트를 그룹화 해서 과도한 호출을 방지하는,

💖최적화와 관련이 있는 💖debounce와 💖throttle.

debounce와 throttle은 이벤트 처리에 매우 유용하며, 구현 시에는 타이머 함수가 사용 된다.

 

 

1. Debounce

짧은 시간 간격으로 이벤트가 연속해서 발생하면(예를 들어 인풋창에 글 쓰기) 일정 시간이 경과한 , 이벤트 핸들러가 한번만 호출 되도록.

→ 짧은 간격으로 발생한 이벤트를 그룹화 하여, 마지막에 한번만 이벤트 핸들러가 호출.

 

* useDebounce 훅은 입력값이 변경될 때마다 즉시 반응하지 않고, 일정 시간 후에 반응하도록 지연시키는 기능을 제공한다. 주로 검색 입력창API 요청 최적화타이핑이 끝난 후 처리 등에서 많이 사용.

 

ex : 사용자가 언제 정확히 입력을 완료했는지 여부는 알 수 없으므로, 일정 시간 동안 입력이 없으면 입력이 끝난 걸로 간주 한다.

 

 

사용자가 입력을 멈춘 뒤 일정 시간(예: 3초)이 지나야 어떤 동작을 실행해.
그런데 그 3초 안에 다시 입력하면? → 기다리던 타이머는 취소되고 새 타이머로 다시 시작해!

💗쉽게 이해해보기 

디바운스(debounce)란?

너무 자주 실행되는 걸 잠깐 기다렸다가 한 번만 실행되게 만드는 거야.

 

 예를들어..

너가 검색창에 뭔가를 타이핑할 때를 떠올려봐.

"cat"를 검색한다고 할 때
c → a → t

글자 하나 칠 때마다 검색 요청이 서버로 가면 너무 많고 불필요하잖아?

그래서 "타이핑이 끝나고 0.5초 동안 멈추면 그때 검색" 하게 해주는 게 디바운스야.

💬 그렇다면.. "디바운스 된 값"이란?

"사용자가 타이핑을 멈추고 일정 시간이 지난 후 확정된 값"
예를 들어, "cat"를 다 치고 0.5초 동안 더 이상 타이핑 안 했을 때,
그때의 값이 디바운스 된 최종 값이야.


기본 문법 구조

const debouncedValue = useDebounce(value, delay);

✓ 매개변수(Parameter)

  • value: 디바운스를 적용할 (예: 검색어, 입력값 등)
  • delay: 지연 시간 (밀리초 단위, 예: 500이면 0.5초)

✓ 반환값(Return)

  • debouncedValue: 실제로 지연된 값

1-1. 단순 예시 코드 

const [inputValue, setInputValue] = useState('');
const debouncedValue = useDebounce(inputValue, 300);
  • inputValue가 바뀌면
  • 300ms 동안 값이 변하지 않을 때만
  • debouncedValue가 변경돼요

이 코드의 흐름

  1. value가 바뀜
  2. 일정 시간(delay) 동안 값이 안 바뀌면
  3. debouncedValue에 그 값이 설정됨
  4. 그 값을 기반으로 뭔가 처리(API 요청 등)할 수 있음

2.  실무에서 쓰이는 사용 예시 코드

2-1 디바운스 훅

// React의 기본 훅을 불러옴: 상태 관리(useState), 사이드 이펙트 처리(useEffect)
import { useState, useEffect } from 'react';

// 디바운싱 처리용 커스텀 훅 정의 (value: 입력값, delay: 지연 시간)
function useDebounce(value, delay) {
  // 디바운싱된 값을 저장할 상태 변수 생성 (초기값은 입력값과 동일)
  const [debouncedValue, setDebouncedValue] = useState(value);

  // value나 delay가 바뀔 때마다 실행되는 useEffect
  useEffect(() => {
    // delay만큼 기다린 뒤에 debouncedValue를 최신 value로 업데이트하는 타이머 설정
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // 다음 useEffect 실행 전에 이전 타이머를 정리해서 중복 실행 방지 (디바운싱 핵심)
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]); // value나 delay가 바뀔 때마다 useEffect 다시 실행됨

  // 디바운싱된 값을 반환
  return debouncedValue;
}

// 다른 컴포넌트에서 이 훅을 사용할 수 있도록 export
export default useDebounce;

디바운스 훅 코드 설명

더보기
function useDebounce(value, delay) {

 

useDebounce라는 이름의 커스텀 훅을 정의하고 있어.

  • value: 디바운스 처리할 원래 값 (예: input 값 -> 인풋 창에서 사용자가 적는 값)
  • delay: 디바운스를 얼마나 기다릴지 시간(ms 단위)

 

  const [debouncedValue, setDebouncedValue] = useState(value);

debouncedValue라는 상태를 만들어.

  • 초깃값은 value야.
  • 나중에 일정 시간이 지난 후에만 이 값이 바뀌게 할 거야.
  • 즉, 바로 value를 반영하지 않고 기다렸다가 반영할 수 있게 하는 상태야.

🔍 초기값이 value인 이유: value를 처음에는 바로 보여주기 위해

  1. useDebounce 훅은 어떤 입력값(value)이 들어오고 나서 delay만큼 기다렸다가 반영하는 구조야.
  2. 하지만 처음 렌더링될 때는 아직 기다릴 필요가 없으니, debouncedValue의 시작값을 그냥 value로 잡아주는 거야.
  3. 그러면 사용자는 처음부터 빈 값이 아니라 입력값 그대로 반영된 상태로 시작하게 돼.

아래 예시 처럼!

const debouncedSearchTerm = useDebounce(searchTerm, 500);
  • 사용자가 검색창에 "apple"을 처음 입력했다고 해.
  • 이때 searchTerm은 "apple"이고, useDebounce는 이 값을 받아와.
  • debouncedValue는 처음에 그냥 "apple"이야. (초깃값이니까!)
  • 그 다음 searchTerm이 "apple pie"로 바뀌면, 500ms 기다린 후 debouncedValue가 "apple pie"로 바뀌는 구조지.
  useEffect(() => {

 useEffect를 사용해서 value나 delay가 바뀔 때마다 아래 동작을 실행해.

  • 입력이 바뀌었을 때마다 타이머를 새로 설정하고, 이전 타이머는 취소할 거야.

 

    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

setTimeout을 사용해서 delay만큼 기다린 후 debouncedValue를 value로 업데이트해.

  • 예를 들어 delay가 500ms라면, 입력이 멈춘 뒤 500ms 후에만 debouncedValue가 바뀌는 거야.
  • 이게 바로 디바운스의 핵심이야!

 

    return () => {
      clearTimeout(handler);
    };

 이건 **클린업 함수(clean-up function)**야.

  • value나 delay가 바뀌기 전에 기존 타이머를 clearTimeout으로 제거해.
  • 만약 사용자가 계속 타이핑 중이면 이전 타이머를 계속 취소해서 setDebouncedValue가 실행되지 않도록 막아주는 역할이야.

 

  }, [value, delay]);

 이 useEffect는 value나 delay가 바뀔 때마다 다시 실행돼.

  • 입력이 변경되면 새 타이머를 설정하고, 이전 타이머를 취소해주는 구조야.

 

  return debouncedValue;

이 훅을 사용하는 컴포넌트는 debouncedValue를 받을 수 있어.

  • 사용자는 입력값(value)이 즉시 바뀌더라도, debouncedValue는 일정 시간 후에만 바뀌게 돼.

 

export default useDebounce;

다른 파일에서도 이 useDebounce 훅을 가져다 쓸 수 있게 export default로 내보내고 있어.

 

 

 

** 클린업 함수

계속 타이핑하면 어떻게 이전 타이머가 취소되고 새로운 타이머만 실행되는 거지?

이걸 하나하나 천천히 설명해줄게.

 

먼저 기억할 개념: useEffect의 클린업 함수

return () => {
  clearTimeout(handler);
};

이 return은 useEffect 내부에서 정의된 클린업 함수야. 이 함수는 두 가지 경우에 호출돼:

  1. 컴포넌트가 언마운트될 때
  2. useEffect가 다시 실행되기 직전에

 

그럼 실제 흐름을 예로 들어볼게:

💬 예시: 사용자가 hello라고 입력할 때

onChange -> "h" 입력
→ useEffect 실행 → setTimeout(500ms 타이머 시작)
→ 500ms 안 지나고

onChange -> "he" 입력
→ useEffect 다시 실행됨 → 이전 타이머 clearTimeout됨
→ 새 타이머(500ms) 다시 시작됨

onChange -> "hel" 입력
→ 또 clearTimeout() → 또 새 타이머
...

이런 식으로 계속 타이핑 중이면 이전 타이머는 실행되지 않고 계속 취소돼.

 

왜 이런 구조가 가능한가?

React는 useEffect 내부에 의존성 배열이 있으면 ([value, delay]) 값이 바뀔 때마다:

  1. 이전 useEffect의 클린업 함수를 먼저 실행하고
  2. 그 다음 새로운 useEffect 본문을 실행해.

즉, 항상 "먼저 기존 타이머 제거 → 새로운 타이머 등록"의 순서가 보장돼.

 

 요약

  • setTimeout은 한번 등록하면 시간이 지나면 실행돼.
  • 근데 clearTimeout으로 그걸 막을 수 있어.
  • 사용자가 입력할 때마다 value가 바뀌고 → useEffect가 다시 실행됨.
  • 이때마다 이전 타이머는 취소되고 새 타이머만 등록되니까,
  • 결국 입력이 끝난 뒤 delay 시간만큼 조용해야만 setDebouncedValue가 실행되는 거야.

 

🔁 코드 요약

useEffect(() => {
  const handler = setTimeout(() => {
    // 뭔가 실행
  }, delay);

  return () => {
    clearTimeout(handler); // ✅ 이 부분이 중요!
  };
}, [value, delay]);

📌 이 clearTimeout(handler)는 언제 실행될까?

1. 컴포넌트가 언마운트될 때

컴포넌트가 화면에서 사라지면(=언마운트), React는 메모리 누수를 막기 위해 useEffect의 cleanup 함수를 실행해 줘.
→ 즉, clearTimeout(handler)가 실행됨.

이건 무대에서 배우가 퇴장할 때 조명을 끄는 거랑 비슷해. 무대에서 안 쓸 거니까 정리해주는 거야.


2. useEffect가 다시 실행되기 직전에

useEffect는 value나 delay가 바뀔 때마다 다시 실행돼.
그런데 새로 실행하기 전에, 기존 타이머부터 정리해야겠지?

→ 그래서 새 setTimeout 만들기 전에 기존 setTimeout을 clearTimeout으로 지워주는 거야.

예시를 보자:

1. value = "a"일 때 -> 타이머 A 설정됨
2. value = "b"로 바뀌면 -> 타이머 B 만들기 전에 A 먼저 clearTimeout!

🔍 왜 이렇게 할까?

React에서 useEffect는 다음과 같은 구조로 작동해:

  1. 처음 value가 설정되면 → 타이머 생성
  2. value가 변경되면 → 기존 타이머 제거 (clearTimeout) → 새 타이머 생성
  3. 컴포넌트가 사라지면 → 타이머 제거 (clearTimeout)

 요약

return () => clearTimeout(handler)는 다음 두 상황에서 실행돼:

  1. 컴포넌트가 언마운트될 때
  2. useEffect가 다시 실행되기 직전에 (의존성 배열의 값이 바뀔 때)

2-2 인풋 창에 불러와서 활용하기

import React, { useState } from 'react';
import useDebounce from './useDebounce';

function SearchComponent() {
  const [input, setInput] = useState('');
  const debouncedInput = useDebounce(input, 500); // 500ms 동안 입력이 없으면 반영

  useEffect(() => {
    if (debouncedInput) {
      // API 요청 등
      console.log('검색 요청: ', debouncedInput);
    }
  }, [debouncedInput]);

  return (
    <input
      type="text"
      value={input}
      onChange={(e) => setInput(e.target.value)}
      placeholder="검색어를 입력하세요"
    />
  );
}

 


그럼 구글 검색창 같은건 바로바로 뜨잖아? 연관검색어가 ? 이건 useDebounce를 안쓴건가 ?

더보기

좋은 질문이야, Jane!

구글 검색창에 연관검색어가 바로 뜨는 건 useDebounce를 쓰지 않았기 때문이 아니라, 아주 짧은 디바운스 시간(예: 100~200ms)을 썼거나, 다른 방식으로 처리했기 때문이야.

정리해서 설명할게:

✅ useDebounce를 쓰는 이유:

  • 사용자가 계속 타이핑할 때마다 API 요청을 보내면 너무 비효율적이니까,
  • 어느 정도 입력이 멈출 때(예: 300ms 동안 입력 없을 때)만 요청을 보내려고 쓰는 거야.

✅ 그런데 구글은?

  • 구글은 사용자가 입력할 때마다 거의 실시간으로 연관검색어를 보여주잖아?
  • 그 말은, 보통 디바운스를 아예 안 쓰거나, 디바운스 시간을 엄청 짧게(100ms 정도) 해서 거의 실시간처럼 보여주는 거야.
  • 그리고 검색어 자동완성 데이터를 로컬 캐시나 서버 사이드에서 미리 예측해서 빠르게 응답하는 구조도 있어.

💡 결론:

  • 구글처럼 실시간 추천을 원하면 useDebounce를 안 쓰거나, 아주 짧은 시간으로 써도 돼.
  • 너가 만드는 서비스에서 성능, 서버 트래픽, UX를 어떻게 조절하고 싶은지에 따라 debounce 시간을 조절하면 돼!

혹시 지금 직접 만드는 검색창 코드가 있다면, 같이 봐줄게!


2. Throttle

 

이벤트가 연속으로 발생하더라도 일정 시간 간격으로 최대 한번만 호출 되도록,

짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서, 호출 주기를 만든다.

 

중간 중간 끊기지는 않았으면 좋겠지만, 뭔가 비효율적일 때 이럴 때 사용하는 것이 스로틀.

 

기본 문법 구조

1. Lodash 사용하기

리액트에서 throttle을 사용하려면 lodash를 설치하고, 상태나 이벤트 핸들러에 적용하면 돼.

 Lodash 설치

npm install lodash

 

 

기본 문법 (Lodash 사용)

import { throttle } from 'lodash';

// throttle 함수: 특정 함수를 특정 시간 간격으로 제한하는 함수
// throttle(함수, 지연시간(밀리초))

const throttledFunction = throttle(() => {
  console.log('스크롤 중');
}, 1000); // 1000ms(1초)마다 한 번만 실행
  • 첫 번째 인자: 실행할 함수
  • 두 번째 인자: 함수 실행 간격을 제한할 시간 (밀리초 단위)

React에서 예시 코드

import React, { useEffect } from 'react';
import { throttle } from 'lodash';

const ThrottledScrollComponent = () => {
  // 스크롤 이벤트가 발생할 때마다 실행할 함수
  const handleScroll = () => {
    console.log('스크롤 중...');
  };

  // throttle 적용: 1초에 한 번만 실행되게 제한
  const throttledScroll = throttle(handleScroll, 1000);

  useEffect(() => {
    // 스크롤 이벤트 리스너 등록
    window.addEventListener('scroll', throttledScroll);

    // 컴포넌트 언마운트 시 이벤트 리스너 정리
    return () => {
      window.removeEventListener('scroll', throttledScroll);
    };
  }, []);

  return (
    <div style={{ height: '2000px' }}>
      <p>스크롤을 내려보세요!</p>
    </div>
  );
};

export default ThrottledScrollComponent;

 핵심 포인트

  1. throttle: 함수를 특정 시간 간격으로만 실행하도록 제한해.
  2. 1000: 1000ms = 1초. 이 시간마다 한 번만 함수를 실행하도록 해줘.
  3. handleScroll: 스크롤 이벤트가 발생할 때마다 실행될 함수야.
  4. throttledScroll: throttle이 적용된 함수로, 1초에 한 번만 실행돼.

차이 비교

hrottle과 debounce는 둘 다 과도한 함수 실행을 제어해서 성능을 높이는 기법이지만, 작동 방식이 다르다.

throttle vs debounce

구분  throttle (쓰로틀) debounce (디바운스)
실행 타이밍 일정 간격으로 계속 실행됨 일정 시간 동안 계속 안 쓰면 한 번 실행됨
예시 상황 스크롤, 마우스 이동처럼 계속 발생하는 이벤트 검색창 입력, 창 리사이즈 처럼 마지막만 중요할 때
비유 정기 알람 시계: 1초마다 한 번만 울림 타이머 알람 시계: 멈춘 뒤 1초 지나야 울림

 

 

 


출처 : 모던딥다이브, 오즈코딩스쿨 학습 자료