낯선 debounce 함수 구조, 코드 흐름 익숙해지기

zimablue·2025년 5월 31일

javascript

목록 보기
30/30

클로저와 고차 함수의 개념이 사용된 코드를 볼 때마다 바로 읽히지 않더라고요.
그래서 기록으로 남기고 자주 보면서 익숙해지려고 해요.

debounce 함수 코드 예

디바운스(debounce)는 빠르게 연속으로 발생하는 이벤트 중 마지막 이벤트만 실행되도록 제어하는 기술이에요.

export const debounce = (fn, wait) => {
  let timeout = null;

  return (...args) => {
    const later = () => {
      timeout = -1;
      fn(...args);
    };

    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = window.setTimeout(later, wait);
  };
};
const handleChangeInput = debounce(event => {
  setState(event.target.value);
}, 300);

debounce 코드 흐름 따라가기

"handleChangeInput의 매개변수는 debounce에서 반환된 함수의 매개변수와 같다." 라는 생각을 계속 가지고 있으면 읽기 수월해져요.

const handleChangeInput = (...args) => {
  const later = () => {
    timeout = -1;
    fn(...args);
  };
  
  if (timeout) {
    clearTimeout(timeout);
  }
  timeout = window.setTimeout(later, wait);
}

handleChangeInput은 이름에서 유추할 수 있듯, input 요소에서 onChange={handleChangeInput} 이런식으로 event를 받아오는 것을 생각해볼 수 있어요.

<input onChange={handleChangeInput} />

// 내부적으로는 ↓ 이렇게 호출되는 것과 같기 때문에 ...args는 event
handleChangeInput(event);

onChange={handleChangeInput}은 input 요소에서 발생한 이벤트 객체를 자동으로 넘겨줘요.
즉, handleChangeInput(event)처럼 호출되는 것과 같아요.

그래서 handleChangeInput 함수의 매개변수 ...argsevent를 받는 구조가 되는 거에요.

export const debounce = (
  fn, //  (event) => { setTitle(event.target.value) } 
  wait // 300
) => {
  let timeout = null;

  return (...args) => { // ...args는 event
    const later = () => {
      timeout = -1;
      fn(...args); // `fn(event)`
    };

    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = window.setTimeout(later, wait);
  };
};
fn은 함수 

debounce의 첫 번째 매개변수인 fn은 함수 (event) => { setTitle(event.target.value) }가 돼요.
여기서 ...argsevent가 전달되고, fn(...args)는 결국 fn(event)가 되는 거에요.
즉, event는 그대로 fn의 매개변수로 들어가는 구조에요.

debounce 사용 흐름 따라가기

debounce를 사용하지 않을 때

사용자가 input에 값을 입력하면, 브라우저는 onChange 핸들러에 event 객체를 넘겨 호출하고 setState로 state를 바꿔줘요.

<input onChange={handleChangeInput} />
const handleChangeInput = (event) => {
  setState(event.target.value);
}

debounce를 사용할 때

debounce를 사용하면 debounce를 호출할 때 내부의 반환되는 부분이 실행돼요.

const handleChangeInput = debounce(event => {
  setState(event.target.value);
}, 300);
export const debounce = (fn, wait) => {
  let timeout = null;

  return (...args) => {
    const later = () => {
      timeout = -1;
      fn(...args);
    };

    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = window.setTimeout(later, wait);
  };
};

위에서 한 번 얘기했듯 아래의 코드와 같다고 보면 돼요.

let timeout = null;

const handleChangeInput = (...args) => {
  const later = () => {
    timeout = -1;
    fn(...args);
  };
  
  if (timeout) {
    clearTimeout(timeout);
  }
  timeout = window.setTimeout(later, wait);
}

여기서 ...argevent와 같고, fn(event) => { setState(event.target.value) }와 같으므로 아래의 코드가 돼요.
wait은 위의 코드에서 300이 들어왔으니 300으로 해주겠습니다.

<input onChange={handleChangeInput} />
let timeout = null;

const handleChangeInput = (event) => {
  const later = () => {
    timeout = -1; // setTimeout 시간에 맞춰 timeout도 줄여주기
    setState(event.target.value);
  };
  
  if (timeout) {
    clearTimeout(timeout); // 이전 `setTimeout`을 취소
  }
  
  timeout = window.setTimeout(later, 300); // 300ms 이후에 setState 실행
}

그럼 input에 입력이 있을 때마다 300ms의 시간을 가진 setTimeout이 생성되고 300ms 이후에 setStatestate로 입력값으로 바꿔요.
하지만 만약 timeout이 있으면 clearTimeout로 이전 setTimeout을 취소하고, 새로운 타이머를 300ms로 설정해요.

0개의 댓글