치춘짱베리굿나이스
Throttle & Debounce 본문
Throttle & Debounce
예제에 useEffect가 들어가서 React 관련 게시글로 뺄까 고민해봤는데 그냥 자바스크립트 / 타입스크립트로 지정하였다
Throttle
지정된 시간 동안 함수를 최대 한 번만 호출하도록 한다 (일정 시간이 지나기 전까지 재호출을 방지한다)
특정 함수를 한번 호출했을 경우 지정한 시간 (예를 들면 1초) 이 흐르기 전까지 재호출이 되지 않도록 막는다
구현하기
...
let timeoutValue;
if (!timeoutValue) {
timeoutValue = setTimeout(() => {
console.log(stateValue);
// 여기에 throttling으로 실행시킬 함수 및 코드 배치
timeoutValue = null; // timeoutValue 초기화
}, 1000);
}
어떻게 된게 예제가 죄다 lodash 라이브러리에서 throttle 구현된거 불러와서 사용하거나, let
const
안 쓰고 var
쓰던 시절 + jquery
밖에 없다..
라이브러리 가져다 사용하기
import { throttle } from 'lodash';
const throttled = throttle(func, cycle);
lodash 라이브러리를 사용하면 위와 같이 쉽게 사용가능하다
작동 방식
timeoutValue
를null
으로 초기화한다- 최초로 함수를 실행하면,
timeoutValue
가null
이기 때문에 조건문 안의 코드 (setTimeout()
) 가 실행된다 setTimeout
의 반환값은timeoutValue
로 들어가므로,timeoutValue
가 더이상null
이 아니기 때문에 함수를 재실행해도 setTimeout이 실행되지 않는다- 지정한 시간이 흐르고 나면
setTimeout
으로 예약한 코드가 실행되고, timeoutValue가 다시 null이 된다 - 따라서 지정한 시간이 흐르고 나서 코드를 재실행할 수 있게 된다
결론
쓰로틀링을 이용해서 무한스크롤 시에 데이터 fetching 횟수 제한을 거는 등 api 호출을 아낄 수 있다
Debounce
이벤트를 그룹화하고, 특정 시간이 지난 후 그중 하나의 이벤트만 발생하도록 한다
input
의 onChange
같은 짧은 시간에 여러 번 일어나는 이벤트를 다룰 때, 지정한 시간동안 일어난 다수의 이벤트 중 마지막 이벤트 하나만 실행되도록 한다
구현하기
...
useEffect(() => {
const timeoutValue = setTimeout(() => {
console.log(stateValue);
// 여기에 debounce로 실행시킬 함수 및 코드 배치
}, 1000); // 두번째 인자로 딜레이 (ms) 지정
return () => {
clearTimeout(timeoutValue);
}; // useEffect unmount될 때 실행되는 clean-up
}, [stateValue]);
ㅇ
아
안
안ㄴ
안녀
안녕 // debounce 안 썼을 때
안녕 // debounce 사용해서 딜레이 줬을 때
디바운싱 함수 내에서 상태값을 사용하는 경우가 대부분이므로, 해당 상태값을 dependency로 갖는 useEffect
로 구현한다
stateValue
가 업데이트될 때마다 useEffect
도 같이 업데이트되며, 그와 함께 clean-up 함수가 실행되면서 이전 입력값에 대한 clearTimeout()
이 실행된다 (이부분은 컴포넌트 생명주기 및 clean-up 함수를 공부하자)
따라서 stateValue
가 짧은 시간 내에 여러번 바뀌면, setTimeout
을 통해 함수 실행을 대기하던 이전 stateValue
들은 전부 clearTimeout()
에 의해 사라졌으므로 설정한 딜레이가 지난 뒤의 마지막 stateValue
에 대해서만 코드가 실행되는 것이다
저 clean-up 함수를 stateValue
를 dependency로 갖는 함수가 아니라, dependency가 없는 useEffect
에서 반환하면 stateValue
가 바뀔 때가 아니라 컴포넌트 언마운트 시에만 clearTimeout()
이 되므로, 디바운싱이 제대로 적용되지 않게 된다
결과값으로 작동 방식 알아보기
ㅇ // 1000ms 이전에 stateValue가 '아' 로 바뀜과 동시에, ㅇ은 clearTimeout() 에 의해 대기가 사라진다
아
안
안ㄴ
안녀
안녕
안녕 // debounce 사용해서 딜레이 줬을 때
stateValue
가 input
태그의 onChange
핸들러에 의해 매 입력 시마다 바뀐다고 할 때, 출력값을 통해 디바운싱이 어떻게 일어나는지 알아보자
- 사용자는 ’안녕' 을 입력하기 위해, 제일 먼저 ‘ㅇ’를 입력한다
setStateValue()
에 의해stateValue
가 ‘’ 에서 ‘ㅇ'으로 업데이트된다, 이 단계에서는 이전stateValue
가 빈 문자열이므로 디바운싱은 신경쓰지 않도록 하자stateValue
가 ‘ㅇ' 으로 변경되면서stateValue
를 dependency로 갖는useEffect
가 함께 실행되고,setTimeout()
에 의해 ‘ㅇ' 을 출력하는 함수의 실행 (첫 번째 인자) 이 1000ms만큼 (두 번째 인자) 미뤄진다. 이때setTimeout()
을 통해 예약한 함수 실행은 1000ms가 흐르지 않은 상태에서clearTimeout()
가 실행될 때 취소됨을 기억하자- 사용자가 다음 글자인 ‘ㅏ' 를 입력하자마자,
setStateValue
가 실행되면서stateValue
가 ‘아' 로 업데이트된다 stateValue
가 업데이트되었으므로useEffect
가 실행되며, 같은 dependency를 갖던 이전useEffect
는clean-up
함수가 실행되고 사라진다- 이때 지정한 clean-up 함수는
clearTimeout()
이었으므로,stateValue
가 ‘ㅇ' 이었던 시절의setTimeout()
예약은 취소된다, 따라서 ‘ㅇ' 은 1000ms가 지나도 출력되지 않는다 - ‘아' 를 출력하는 함수의 실행이
setTimeout()
에 의해 예약되며, 다음 글자인 ‘ㄴ' 을 입력하면 4 ~ 6번 과정이 반복되면서 ‘아' 의 출력 예약도 사라진다 - 위의 과정을 반복하면 결국 처음 입력을 시작한 시점부터 1000ms 뒤에 입력되어있는 값인 ‘안녕' 만이 출력되는 것이다
결론
setTimeout()
함수의 첫 번째 인자로 지정한 함수 (예시에서는 () ⇒ { console.log(stateValue) }
) 는 두 번째 인자로 지정한 시간 (1000ms) 이 흐른 뒤에만 호출되며, 따라서 1000ms 가 흐르고 함수를 호출하는 시점의 stateValue
의 값을 기준으로만 동작하는 것이다
디바운싱을 이용해서 검색창에서 사용자가 입력을 할 때, 특정 시간마다만 API를 호출하거나, 버튼을 빠르게 연타했을 때 상태값이 빠르게 변경되지 않도록 할 수 있다
커스텀 훅으로 만들기
export const useDebounce = (func, delay, dependency) => {
useEffect(() => {
const timeoutValue = setTimeout(() => {
func();
}, delay);
return () => {
clearTimeout(timeoutValue);
};
}, [func, dependency, delay]);
};
...
// 컴포넌트 내부
useDebounce(() => { console.log(stateValue) }, 1000, [stateValue]);
위의 useEffect 부분을 커스텀 훅으로 분리하면 간단하게 한 줄로 처리 가능하다
디바운스는 다른 컴포넌트에서도 종종 쓸 일이 있기 때문에, 훅으로 분리하여 코드를 간결하게 만들 수 있다
func()
가 연산량이 많은 함수라면, 커스텀 훅 내에서 useCallback()
으로 감싸서 사용하는 것을 추천한다
참고자료
[React]React Hook에서 Throttle, Debounce 사용
'Javascript + Typescript > 이론과 문법' 카테고리의 다른 글
자바스크립트의 대부분 요소는 객체인가요? (0) | 2022.07.26 |
---|---|
require, import, export (0) | 2022.07.25 |
비동기 처리와 Promise (0) | 2022.05.13 |
[Typescript] Type vs Interface (0) | 2022.05.09 |
spread, rest (0) | 2022.04.12 |