짧은 시간 간격으로 연속해서 이벤트가 발생했을때 과도한 이벤트 호출을 방지하기 위해 쓰이는 기법들인 Throttling & Debouncing
에 대해 알아보자.
그전에 Lodash를 알고 넘어가자.
일반적인 유틸 함수들을 모아서 제공해주는 라이브러리
이다.
debounce나 throttle 처리를 할 때 lodash에서 제공하는 함수를 이용한다.
이 외에 데이터타입 등을 순회하면서 각 요소마다 원하는 기능을 실행할 때에도 사용한다.
설치법은 간단하다.
npm install --save lodash
짧은 시간 간격으로 연속해서 발생한 이벤트들을 일정시간 딜레이를 줘서 호줄되지 않게 해주는 작업이다.
주로 스크롤 이벤트에서 많이 쓰임.
연속으로 호출되는 함수들 중에 마지막에 호출되는 함수(또는 제일 처음 함수)만 실행되도록 하는 작업이다.
주로 실시간 입력값, 화면 resize 이벤트에서 쓰임.
import React, { useCallback, useEffect, useState } from "react";
import _ from "lodash";
type ControlDelay = (callback: (...args: any[]) => void, delay: number) => any;
export default function Company() {
const [selected, setSelected] = useState("customThrottle"); // customThrottle, customDebounce, lodashThrottle, lodashDebounce
const [searchText, setSearchText] = useState("");
const [inputText, setInputText] = useState("");
console.log("searchText:", searchText);
const throttle: ControlDelay = (callback, delay) => {
let timerId: NodeJS.Timeout | null = null;
let latestArgs: any[] = [];
return (...args: any[]) => {
// For trailing edge
latestArgs = args;
if (timerId) return;
// For Leading edge
callback(...args);
timerId = setTimeout(() => {
if (!_.isEqual(latestArgs, args)) callback(...latestArgs);
timerId = null;
}, delay);
};
};
const debounce: ControlDelay = (callback, delay) => {
let timerId: NodeJS.Timeout | null = null;
return (...args: any[]) => {
if (timerId) clearTimeout(timerId);
timerId = setTimeout(() => {
callback(...args);
}, delay);
};
};
const selectEventControl = (delay: number) => {
switch (selected) {
case "customThrottle":
return throttle((text) => setSearchText(text), delay);
case "customDebounce":
return debounce((text) => setSearchText(text), delay);
case "lodashThrottle":
// _.throttle 의 기본 옵션은 leading & trailing edge
return _.throttle((text) => setSearchText(text), delay, {
leading: true,
trailing: true,
});
case "lodashDebounce":
// _.debounce 의 기본 옵션은 trailing edge
return _.debounce((text) => setSearchText(text), delay, {
leading: false,
trailing: true,
});
default:
break;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleSearchText = useCallback(selectEventControl(2000), [selected]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
handleSearchText(e.target.value);
setInputText(e.target.value);
};
// resize 이벤트 핸들러
const handleResize = _.debounce(() => console.log("resize 완료"), 1000);
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => {
handleResize.cancel(); // 현재 동작중인 스케쥴 동작 취소. 메모리 누수 방지.
window.removeEventListener("resize", handleResize); // resize 이벤트리스너 제거
};
}, [handleResize]);
return (
<div style={{ paddingLeft: 20, paddingRight: 20 }}>
<h1>Input Text 및 Resize 이벤트 예제</h1>
<h2>원하는 Throttle, Debounce 함수를 선택하세요</h2>
<label style={{ display: "block" }}>
<input
checked={selected === "customThrottle"}
onChange={() => setSelected("customThrottle")}
type={"radio"}
/>
customThrottle
</label>
<label style={{ display: "block" }}>
<input
checked={selected === "customDebounce"}
onChange={() => setSelected("customDebounce")}
type={"radio"}
/>
customDebounce
</label>
<label style={{ display: "block" }}>
<input
checked={selected === "lodashThrottle"}
onChange={() => setSelected("lodashThrottle")}
type={"radio"}
/>
lodashThrottle
</label>
<label style={{ display: "block" }}>
<input
checked={selected === "lodashDebounce"}
onChange={() => setSelected("lodashDebounce")}
type={"radio"}
/>
lodashDebounce
</label>
<br />
<input
placeholder="입력값을 넣고 쓰로틀링/디바운싱 테스트 해보세요"
style={{ width: "100%" }}
onChange={handleChange}
type={"text"}
/>
<p>Search Text: {searchText}</p>
<p>Input Text: {inputText}</p>
</div>
);
}