짧은 시간 간격으로 연속해서 이벤트가 발생했을 때 과도한 이벤트 핸들러 호출을 방지하는 기법이다. 예를 들어 인스타그램에 좋아요를 누를 때마다 서버에 데이터를 갱신해달라는 요청을 보내면, 엄청난 부하가 발생할 것이다.
(1) Throttling
짧은 시간 간격으로 연속해서 발생한 이벤트를 일정 시간(delay)을 두고 그룹화하여 처음(Leading Edge), 마지막(Trailling Edge), 또는 처음과 마지막(Leading & Trailing Edge) 이벤트 핸들러만 호출하는 것이다.
즉, 실행중인 타이머가 있으면 바로 return해서 종료시키고,
실행중인 타이머가 끝난 후 재요청을 할 수 있다.
let timerId = null;
const throttle = (delay) => {
// timerId가 있으면 바로 종료
if (timerId) {
return;
}
console.log(`API요청 실행! ${delay}ms 동안 추가요청 안받음`);
timerId = setTimeout(() => {
console.log(`${delay}ms 지남 추가요청 받음`);
timerId = null;
}, delay);
};
return (
<div>
<button onClick={() => throttle(2000)}>쓰로틀링 버튼</button>
</div>
)
(2) Debouncing
짧은 시간 간격으로 연속해서 이벤트가 발생하면 이벤트 핸들러를 호출하지 않다가 마지막 이벤트로부터 일정 시간(delay)이 경과한 후에 한 번만 호출하도록 하는 것이다. ex) 리사이징
즉, 실행중인 타이머가 있으면 clearTimeout을 통해 기존 타이머를 제거하고
마지막에 요청된 타이머로부터 delay 시간이 지난 후 한 번만 호출한다.
const debounce = (delay) => {
if (timerId) {
// 할당되어 있는 timerId 타이머 제거
clearTimeout(timerId);
}
timerId = setTimeout(() => {
// timerId에 새로운 타이머 할당
console.log(`마지막 요청으로부터 ${delay}ms지났으므로 API요청 실행!`);
timerId = null;
}, delay);
};
return (
<div>
<button onClick={() => throttle(2000)}>쓰로틀링 버튼</button>
<button onClick={() => debounce(2000)}>디바운싱 버튼</button>
</div>
)
(3) 메모리 누수 방지
메모리 누수란 필요하지 않은 메모리를 계속 점유하고 있는 현상으로, 예를 들어 리액트에서 페이지를 이동하면 기존 컴포넌트는 언마운트된다. 그런데 setTimeout
을 통해 타이머 동작 중에 페이지를 이동하게 되면 기존 페이지가 언마운트 되었음에도 불구하고 요청된 타이머는 계속 실행하여 메모리를 차지하고 있다.
이러한 메모리 누수를 방지하기 위해 useEffect
를 사용하여 언마운트될 때 clearTimeout
을 통해 기존 timerId에 해당하는 타이머를 종료시켜줄 수 있다.
useEffect(() => {
return () => {
// 페이지 이동 시 실행
if (timerId) {
clearTimeout(timerId);
}
};
}, [timerId]);
throttling과 debouncing을 해주는 라이브러리
_
언더바를 통해 import 해온다.yarn add lodash
import "./App.css";
import { useState, useCallback } from "react";
import _ from "lodash";
function App() {
const [searchText, setSearchText] = useState("");
// useCallback 함수로 감싸준다.
// 2초 후에 Input의 Value값을 인자로 받아 setSearchText에 넣어준다.
const handleSearchText = useCallback(
_.debounce((text) => setSearchText(text), 2000),
[]
);
const handleChange = (e) => {
handleSearchText(e.target.value);
};
return (
<div>
<input
onChange={handleChange}
type="text"
/>
<p>Search Text: {searchText}</p>
</div>
);
}
export default App;
오늘은 React 강의를 들으며 중간에 지나쳤던 부분이 있어서 그 부분을 다시 듣고, 기존에 특강으로 올라왔던 리액트 강의들을 다시 들어보면서 리액트에 대해 복습하는 시간을 가졌다.
스탠다드반 수업을 들으면서 강의에서는 배울 수 없었던 심화 내용도 들었다. 코드를 더 효율적으로 짤 수 있는 방법들을 많이 알게되었다. 이걸 다음 프로젝트에서 적용할 수 있을지에 대해서는 자신이 없지만... 여러 번 복습해 봐야겠다..