lodash debounce를 활용하여 성능 향상시키기
개요
현재 진행중인 프로젝트 STAYMATE에서 아주 심각한 문제가 발견되었다.
이전 글에서 카메라를 사용하기 위해 react-qr-reader라이브러리를 썼는데,
호출이 과도하게 많아지는 문제점을 발견하게 되었다.
이러한 문제점을 발견하고 어떻게 해결을 할까 고민하기 시작했다.
무한 호출을 막을 수 있는 방법
우선 폭포처럼 내리는 무한 호출을 막을 수 있는 방법은, 유한 호출 될 수 있도록 만들면 된다.
연속으로 발생한 호출은 한 번만 인정해주거나 혹은 몇 초에 한 번씩 인식하도록 하거나.
이러한 방법을 해결할 수 있는 개념이 debounce와 throttle이다.
Debounce와 throttle이란?
Debounce
연속적으로 발생한 이벤트를 하나로 처리하는 방식으로,
주로 처음이나 마지막에 실행된 함수만 실행한다.
예를 들면 아래의 예시 코드를 보도록하자.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
console.log(e.target.value);
};
return (
<div className="App">
<input type="text" value={value} onChange={handleChange} />
</div>
);
}
단순히 입력을 useState로 관리하는 것처럼 보이지만,
사실 콘솔에 내용을 찍어보면 이렇게 호출이 상당히 많이 일어나는 걸 볼 수 있다.
그저 3글자 입력값 "소금빵"이라는 단어를 입력했을 뿐인데, ㅅ,소,소그,소금, ... 이런식으로 호출이 일어났다.
1번만 호출해도 될 것을 9번이나 호출하게 된다.
그래서 이때 debounce를 사용할 수 있다.
"소금빵"이라고 3글자가 완전히 입력되었을 때 API를 호출하게끔하는것이다.
좋은 예제가 있어 활용해보았다. setTimeout과 clearTimeout을 활용해서 딜레이를 주었다.
function debounce<T extends (...args: any[]) => void>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout>;
return function (...args: Parameters<T>) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
설정한 delay 시간 내에 함수를 재실행하게 된다면,
내부 함수에 의해 살아있는 timeout함수가 clear 되어 무효화된다.
만약 시간 내 아무런 호출이 없었다면 가장 마지막으로 호출된 setTimeout 내부의 함수가 실행될 것이다.
그리고 해당 함수를 아까 기존 코드에 적용해주었다.
import { useState, useCallback, ChangeEvent } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState<string>("");
const debouncedLog = useCallback(
debounce((val: string) => {
console.log(val);
}, 800),
[]
);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setValue(newValue);
debouncedLog(newValue);
};
return (
<div className="App">
<input type="text" value={value} onChange={handleChange} />
</div>
);
}
아래와 같이 한 번만 호출하는 것을 볼 수 있다.
throttle
이벤트를 일정 주기마다 발생하도록 하는 방식으로,
특정 시간에 한 번만 실행된다.
예를 들면 100ms가 주어지면 이 시간 동안 한 번만 실행되는 것이다.
둘 다 어쨌든 호출 횟수를 줄일 수 있도록해준다.
필자는 이 중에 debounce를 사용할 예정이다.
lodash debounce 활용하기
debounce를 그냥 사용해도 좋지만, lodash라이브러리를 활용해 간단하게 사용해보려고한다.
프로젝트를 빨리 배포하는 것이 목적이기도하고, 사용법이 쉬운 라이브러리를 사용하도록 하자!
우선 기존 코드를 보여주려고한다.
<QrReader
constraints={{ facingMode: selected }}
scanDelay={1000}
className="qr"
onResult={(result, error) => {
if (result) {
console.log(`[SUCCESS] QR 문자 : `, result.getText());
}
if (error) {
console.log("[ERROR] QR 코드를 정확하게 인식해주세요.");
}
}}
/>
이제 이를 debounce 적용을 하면 아래와 같이 나타낼 수 있다.
<QrReader
constraints={{ facingMode: selected }}
scanDelay={1000}
className="qr"
onResult={(useCallback(
debounce((result, error) => {
if (result) {
console.log(`[SUCCESS] QR 문자 : `, result.getText());
}
if (error) {
console.log("[ERROR] QR 코드를 정확하게 인식해주세요.");
}
}, 500),
[]
)}
/>
결과
적용했을 때, 호출이 줄어들었고 카메라 인식 속도가 훨씬 빨라졌다.
게다가 폭포처럼 쏟아졌던 콘솔로그가 깔끔해졌다.
맺으며
이번 포스팅에서는 throttle보다 debounce에 더 집중해서 글을 작성했는데,
throttle은 무한 스크롤을 할때 유용하게 쓰인다고 한다.
기회가 된다면 사용해보고 싶다. debounce와 throttle을 적절히 잘 활용하면,
사용자의 경험도 개선할 수 있다는 이점을 확연히 깨달았다.