디바운싱과 쓰로틀링 모두 UI에서 발생하는 이벤트를 제어하는 방법이다.
과도하게 이벤트 처리 함수(콜백함수)가 호출되지 않도록 하여 부하를 방지하기 위해 쓰이는 방법이다.
👉 둘 다 짧은 시간 간격으로 연속해서 이벤트가 발생했을 때 과도한 이벤트 핸들러 호출을 방지하는 기법
- 연속해서 이벤트가 발생할 때, 첫 이벤트 처리 후 일정 시간 동안 나머지 이벤트를 무시하는 방식
- 주로 사용되는 예 : 무한 스크롤
[type 1: Leading Edge]
[type 2: Trailing Edge]
const Home = () => {
// 생략
let timerId = null;
const throttle = (delay) => {
if (timerId) {
// timerId가 있으면 바로 함수 종료
return;
}
console.log(`API 요청 실행! ${delay}ms 동안 추가요청은 안받습니다!`);
timerId = setTimeout(() => {
console.log(`${delay}ms 지남 추가 요청 받습니다!`);
timerId = null; // 2초 지나면 timerId가 null 이 되어 함수 실행 가능해짐
}, delay);
};
// 생략
return (
<StDiv>
<h1>Button 이벤트 예제</h1>
<button onClick={() => throttle(2000)}>쓰로틀링 버튼</button>
<button onClick={() => debounce(2000)}>디바운싱 버튼</button>
<div>
<button onClick={() => navigate("/company")}>페이지 이동</button>
</div>
</StDiv>
);
};
export default Home;
Q. setTimeout이 메모리 누수를 유발하는가?
A. 하나의 페이지에서 페이지 이동 없이 setTimeout을 동작시키고, 타이머 함수가 종료될 때까지 기다린다면 메모리 누수는 없다. 그런데 페이지 이동 전에 setTimeout으로 인해 타이머가 동작중인 상태에서 clearTimeout 을 해주지 않고 페이지를 이동하면 컴포넌트는 언마운트 되었음에도 불구하고 타이머는 여전히 메모리를 차지하고 동작하고 있다. 이 경우 메모리 누수가 발생했다고 말할 수 있다. (cf. 리액트로 만든 SPA 웹사이트는 페이지 이동 시 컴포넌트가 언마운트 된다.)
다음과 같이 클린업을 통해 컴포넌트가 언마운트 될 때 clearTimeout을 실행한다면, 메모리 누수가 발생하지 않는다.
const Home = () => {
const [state, setState] = useState(true);
const navigate = useNavigate();
//메모리 누수 방지 ⭐️
useEffect(() => {
return () => {
// 홈 컴포넌트 언마운트 될 때!
if (timerId) clearTimeout(timerId);
};
});
let timerId = null;
const throttle = (delay) => {
if (timerId) {
// timerId가 있으면 바로 함수 종료
return;
}
console.log(`API 요청 실행! ${delay}ms 동안 추가요청은 안받습니다!`);
timerId = setTimeout(() => {
console.log(`${delay}ms 지남 추가 요청 받습니다!`);
timerId = null; // 2초 지나면 timerId가 null 이 되어 함수 실행 가능해짐
}, delay);
};
// 생략
- 연속해서 이벤트가 발생할 때 전체 이벤트를 하나의 그룹으로 묶어서 '처음 or 마지막' 하나만 처리
- 주로 사용되는 예 : 검색어 자동완성, 화면 resize 이벤트
const Home = () => {
// 생략
let timerId = null;
// 반복적인 이벤트 이후 delay 가 지나면 함수 실행
const debounce = (delay) => {
if (timerId) {
// 할당되어있는 timerId에 해당하는 타이머 제거
clearTimeout(timerId);
}
timerId = setTimeout(() => {
console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 API 요청 실행`);
timerId = null;
}, delay);
};
return (
<StDiv>
<h1>Button 이벤트 예제</h1>
<button onClick={() => throttle(2000)}>쓰로틀링 버튼</button>
<button onClick={() => debounce(2000)}>디바운싱 버튼</button>
<div>
<button onClick={() => navigate("/company")}>페이지 이동</button>
</div>
</StDiv>
);
};
export default Home;
setTimeout을 통해 쓰로틀링과 디바운싱을 구현할 수 있지만, lodash 라는 라이브러리를 통해 더 쉽게 구현할 수 있다.
yarn add lodash
import { useCallback, useState } from "react";
import _ from "lodash";
function App() {
const [searchText, setSearchText] = useState("");
const [inputText, setInputText] = useState("");
// useCallback을 써야 정상 동작 한다. 메모이제이션을 해야 함.
const handleSearchText = useCallback(
_.debounce((text) => {
setSearchText(text);
}, 2000),
[]
); // 2초 뒤에 실행
const handleChange = (e) => {
setInputText(e.target.value);
handleSearchText(e.target.value);
};
return (
<div>
<h1>디바운싱 예제</h1>
<input
placeholder="입력값을 넣고 디바운싱 테스트를 해보세요"
type="text"
onChange={handleChange}
/>
<p>Search Text : {searchText}</p>
<p>input Text : {inputText}</p>
</div>
);
}
export default App;