짧은 시간 간격으로 연속해서 이벤트가 발생했을 때 과도한 이벤트 핸들러 호출을 방지하는 기법
setTimeout()
을 활용해 구현,원리이해, 적용까지 해보자
타입 | 설명 |
---|---|
Leading Edge | 이벤트가 처음 발생할 때 핸들러가 실행되고 주어진 delay동안 동일한 이벤트 무시 |
Trailing Edge | 이벤트가 반복적으로 실행될 때 주어진 시간(delay)이 지나면 마지막에 이벤트를 처리 |
Leading & Trailing Edge | 처음과 끝에 실행되고 그 사이에 이벤트는 무시 됨 |
const App() {
let timerId = null;
const throttling = (delay) => {
// timerId 에 값이 있으면 종료
if(timerId) return;
// 실행하고자 하는 로직
console.log(`API요청 실행, ${delay}ms 동안 추가요청 안받음`);
// timerId에 setTimeout을 걸어놓기
timerId = setTimeout(() => {
console.log(`${delay}ms 지남, 추가 요청 받음`);
timerId = null;
}, delay)
}
return(
<button onClick={() => throttling(2000)}>Throttling Btn</button>
)
}
const App() {
let timerId = null;
const debounce = (delay) => {
if(timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 api 요청 실행`);
timerId = null;
}, delay);
}
return(
<button onClick={() => debounce(2000)}>Debouncing Btn</button>
)
}
필요하지 않은 메모리를 계속 점유하고 있는 현상
쓰로틀링과 디바운싱에서 setTimeout
을 자주 사용하기 때문에, 이 함수의 사용으로 인한 메모리 누수 가능성을 이해하는 것이 중요
하나의 페이지에서 페이지 이동 없이 setTimeout
을 동작시키고 타이머 함수가 종료될 때까지 기다린다면 메모리 누수는 없다.
⭐React로 만든 SPA 앱은 페이지 이동 시 컴포넌트가 언마운트 되는데, 페이지 이동 전에 ⭐setTimeout
으로 인해 타이머가 동작중인 상태에서 clearTimeout
을 해주지 않고 페이지를 ⭐이동한다면, 컴포넌트는 언마운트 됐음에도 타이머는 여전히 메모리를 차지하고 동작한다.
→ 이 경우 메모리 누수에 해당한다.
const App() {
let timerId = null;
const throttling = (delay) => {
if(timerId) return;
console.log(`API요청 실행, ${delay}ms 동안 추가요청 안받음`);
timerId = setTimeout(() => {
console.log(`${delay}ms 지남, 추가 요청 받음`);
timerId = null;
}, delay)
}
const debounce = (delay) => {
if(timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 api 요청 실행`);
timerId = null;
}, delay);
}
return(
<button onClick={() => debounce(2000)}>Debouncing Btn</button>
<button onClick={() => throttling(2000)}>Throttling Btn</button>
<button onClick={() => {navigate("/other")}}>다른 페이지로 이동</button>
)
}
/* debounce, throttling 둘중 아무거나 실행하고 다른 페이지로 이동 버튼을 누른다면
해당 컴포넌트가 언마운트 됨에도 불구하고 실행된다. */
이를 해결하기 위해선 useEffect
의 cleanup 함수를 이용하면 된다.
const App() {
let timerId = null;
const throttling = (delay) => {
if(timerId) return;
console.log(`API요청 실행, ${delay}ms 동안 추가요청 안받음`);
timerId = setTimeout(() => {
console.log(`${delay}ms 지남, 추가 요청 받음`);
timerId = null;
}, delay)
}
const debounce = (delay) => {
if(timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 api 요청 실행`);
timerId = null;
}, delay);
}
// useEffect를 사용한 cleanup
useEffect(() => {
return() => {
if(timerId) {
clearTimeout(timerId);
}
}
}, []);
return(
<button onClick={() => debounce(2000)}>Debouncing Btn</button>
<button onClick={() => throttling(2000)}>Throttling Btn</button>
<button onClick={() => {navigate("/other")}}>다른 페이지로 이동</button>
)
}
import _ from 'lodash';
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom';
const LodashPage = () => {
const navigate = useNavigate();
let timerId = null;
const throttling = _.throttle(() => {
console.log(`api 요청 실행 2000ms 동안 추가요청 안받음!`);
}, 2000);
const debounce = _.debounce(() => {
console.log(`마지막 요청으로부터 2000ms 지났으므로 api 요청 실행`);
}, 2000);
useEffect(() => {
return () => {
if(timerId) {
clearTimeout(timerId);
}
}
}, []);
return (
<>
<div>Lodash</div>
<button onClick={throttling}>쓰로틀링 버튼</button>
<button onClick={debounce}>디바운싱 버튼</button>
<div>
<button onClick={() => {navigate("/")}}>Home페이지 이동</button>
<button onClick={() => {navigate("/lodash")}}>lodash연습페이지 이동</button>
</div>
</>
)
}
export default LodashPage
import _ from "lodash";
import { useState, useCallback } from "react";
const Company = () => {
const [searchText, setSearchText] = useState("");
const [inputText, setInputText] = useState("");
// 둘다 실행해서 차이점을 알아 둘 것
// const handleSearchText = _.debounce((text) => setSearchText(text), 2000);
const handleSearchText = useCallback(_.debounce((text) => setSearchText(text), 200),[]);
// + useCallback : memoization인데 이제 함수를 저장!
// + useMemo : 값을 memoization
const handleChange = (e) => {
setInputText(e.target.value);
handleSearchText(e.target.value);
};
return (
<div
style={{
paddingLeft: 20,
paddingRight: 20,
}}
>
<h1>디바운싱 예제</h1>
<br />
<input
placeholder="입력값을 넣고 디바운싱 테스트를 해보세요."
style={{ width: "300px" }}
onChange={handleChange}
type="text"
/>
<p>Search Text: {searchText}</p>
<p>Input Text: {inputText}</p>
</div>
);
}
export default Company