⚡Debounce란
Debounce
란 일정 시간 동안 연속적으로 발생했던 이벤트들 중
마지막만 실행시켜 과다한 호출이나 렌더를 막아 최적화하는 기술입니다.
예를 들어 이번 개인 프로젝트 중 입력 시 중복체크를 위해 API를 호출하는데
한 글자 타이핑할 때마다 API를 호출하는 것이 부담되어 Debounce를 도입하여 해결했습니다.
const debounce = <T extends (...args: any[]) => any>(fn: T, delay: number) => {
let timeout: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>): ReturnType<T> => {
let result: any;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
result = fn(...args);
}, delay);
return result;
};
};
Typescript로 구현한 debounce
함수입니다.
여러 타입 때문에 복잡해 보이지만 클로저만 알고 있다면 간단하게 해석할 수 있습니다.
설정한 delay
시간 내에 함수를 재실행하게 된다면
내부 함수에 의해 살아있는 timeout
함수가 clear
되어 무효화됩니다.
만약 시간 내 아무런 호출이 없었다면 가장 마지막으로 호출된 setTimeout 내부의 함수가 실행될 것입니다.
📌 React에서 Debounce 구현하기
특히나 상태 변화에 의해 렌더링이 발생하는 React에서는 Debounce가 굉장히 유용합니다.
const [state, setState] = useState('');
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setState(e.target.value);
}
console.log(state);
return <input onChange={onChange} />
이렇게 간단한 입력에도 수많은 렌더링이 발생하는 리액트에서는
특히나 onChange
같은 함수에 Debounce
를 사용하면 더욱더 효율적입니다.
import { useState } from 'react';
const debounce = <T extends (...args: any[]) => any>(fn: T, delay: number) => {
let timeout: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>): ReturnType<T> => {
let result: any;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
result = fn(...args);
}, delay);
return result;
};
};
// API를 호출해 중복 체크하는 예시
const promiseFun = async (value: string) => {
console.log('Call');
await new Promise((resolve) => setTimeout(resolve, 300));
return 'abcd' === value;
};
export default function App() {
const [check, setCheck] = useState(false);
const [debounceState, setDebounceState] = useState('');
const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const result = await promiseFun(e.target.value);
setDebounceState(e.target.value);
setCheck(result);
};
const debouncedOnChange = debounce<typeof onChange>(onChange, 500);
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh',
}}
>
<h2>중복 문자열 "abcd"</h2>
<h3>state : {debounceState}</h3>
<h3>check : {check ? 'true' : 'false'}</h3>
<input type="text" onChange={debouncedOnChange} />
<hr />
<button disabled={check}>BUTTON</button>
</div>
);
}
API 호출을 가정한 promise
함수를 불러와 중복 체크하는 기능을 구현한 코드입니다.
직접 입력해 보면 debounce에 의해 마지막 입력까지 렌더와 함수 호출이 없는 것을 확인할 수 있습니다.
⚡ 참고사항
📌 lodash 라이브러리 Debounce
$ npm install @types/lodash
// -----------------------------------------
import { debounce } from 'lodash';
...
const debouncedOnChange = debounce(onChange, 500);
유틸 라이브러리로 유명한 lodash에도 debounce 기능이 내장되어 있어
별도 코드 작성 없이도 기능을 구현할 수 있습니다.
내부 코드도 위에서 소개한 것과 같은 원리로 작동됩니다.
📌 useCallback() warning
다른 상태에 의해 리랜더링이 일어나 debounce 함수 객체를 재생성해 timer
가 초기화될 위험이 있습니다.
이럴 때 주로 useCallback()으로 함수를 메모이제이션 합니다.
const debouncedOnChange = useCallback(
debounce((...) => {...},500)
,[])
하지만 이렇게 사용하게 된다면 다음과 같은 warning을 볼 수 있습니다.
React Hook useCallback received a function whose dependencies are unknown.
Pass an inline function instead. (react-hooks/exhaustive-deps)
이 경고는 debounce로 감싼 함수에서 종속성을 감지하지 못해 발생합니다.
const debouncedOnChange = useMemo(
() => debounce((...) => {...},500)
,[])
이는 useMemo를 통해 debounce 함수를 반환함으로써 해결할 수 있습니다.
'React' 카테고리의 다른 글
[React] Suspense을 사용해 선언적으로 로딩 화면 구현하기 (4) | 2023.03.23 |
---|---|
[React] Recoil의 effects를 이용해 localStorage, sessionStorage에 데이터 저장하기 (0) | 2023.03.22 |
[React] input 한 글자 입력 시 focus out 되는 현상 (0) | 2023.03.02 |
[React + Craco + TS] Alias 절대경로 + 폴더 index 절대경로 설정 방법 (0) | 2023.02.14 |
[React] 렌더링 배치, 리렌더링 최적화 기초 (0) | 2023.02.11 |
댓글