짧은 시간 간격으로 연속해서 이벤트가 발생했을 때, 과도한 이벤트 핸들러 호출을 방지하는 기법이다.
자바스크립트에서는 Timer Web API인 setTimeout
메소드를 사용해서 Throttlint과 Debouncing을 구현한다.
짧은 시간 동안 연속해서 발생한 이벤트들을 일정 시간 단위(delay)로 그룹화하여, 처음 또는 마지막 이벤트 핸들러만 호출
하도록 하는 것
예시: 무한스크롤
짧은 시간 동안 연속해서 발생한 이벤트를 호출하지 않다가, 마지막 이벤트로부터 일정 시간(delay) 이후에 한번만 호출
하도록 하는 것
예시: 입력값 실시간 검색, 화면 resize 이벤트
메모리 누수란, 필요하지 않은 메모리를 계속 점유하고 있는 현상을 말한다.
상황에 따라 다르다.
타이머 함수가 종료될 때까지 기다린다면
메모리 누수는 없다.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
는 성능이 보장되어 있는 다양한 메소드를 제공하는 JavaScript 라이브러리이다.
array, collection, date 등 데이터 구조 뿐만 아니라, throttle
과 debounce
메소드도 제공한다.
프로젝트에서 form 제출 이벤트를 처리할 때 Throttling과 Debouncing에 대해 깊게 고민해본 적이 없었던 것 같다.
이벤트 핸들러 연속 호출 방지를 할 때는 단순히 setTimeout으로 button을 disabled
했다.
이제는 이러한 개념에 대해 알았으니 다양한 로직에 적용해볼 수 있겠다.