아주 짧은 시간에 엄청나게 많은 횟수 혹은 여러번 발생하는 이벤트가 의도한게 아니라면 제한시켜주는게 성능상 좋다.
이것에 대한 제한 방법으로 Throttle
과 Debounce
가 있다.
두가지는 비슷한듯 하지만 개념이 있다.
디바운스는 흔히 보통 엘리베이터를 예로 든다. 엘리베이터는 문이 닫히면 올라가거나 내려간다. 하지만 만약 문이 닫히기 전에 사람이 탄다면 다시 문이열리고 엘리베이터는 닫힐 때마다 기다린다.
즉 이벤트가 일정기간동안 여러번 발생했을 때 마지막 (혹은 처음) 으로 발생한 이벤트만 동작하게 한다는 개념이 Debounce이다.
import { useState, useEffect } from 'react'
export default function useDebounce(func, wait) {
const [id, setId] = useState(null)
useEffect(() => {
return () => {
clearTimeout(id)
}
}, [id])
return (...args) => {
if (id) {
clearTimeout(id)
}
setId(
setTimeout(() => {
setId(null)
func(...args)
}, wait)
)
}
}
useDebounce라는 hook을 구현해봤는데
Trottle
은 일정한 기간마다 이벤트가 여러번 발생했을 때 한번만 이벤트를 호출하는 방식이다.
즉 1초에 100번이 호출되도 한번만 호출하는 식.
Debounce
가 1초중 마지막 호출된 놈이 있으면 다시 그놈의 1초를 기다렸다가 더이상 호출되는 놈이 없으면 그놈을 호출하는 식이라면 Throttle은 마지막놈과는 관계없이 그냥 일정기간에 한번 호출되는 것이기 때문에 개념이 다르다.
따라서 Throttle은 보통 스크롤같은 이벤트에 많이 활용된다.
import { useState, useEffect, useRef } from 'react'
export default function useThrottle(func, wait) {
const [id, setId] = useState(null)
const [previous, setPrevious] = useState(Date.now())
const remaining = useRef(wait)
let now = previous
let diff = 0
useEffect(() => {
return () => {
clearTimeout(id)
now = Date.now()
diff = wait - (now - previous)
remaining.current = diff < wait && diff > 0 ? diff : 0
}
}, [id, previous])
return (...args) => {
if (remaining.current <= 0) {
func(...args)
setPrevious(Date.now())
} else {
setId(
setTimeout(() => {
func(...args)
}, remaining.current)
)
}
}
}
debounce
와는 다르게 일정시간이 되면 무조건 한번은 실행이 되어야 한다.setTimeout
의 시간 파라미터가 변경이 되어야 한다. // When a sequence of calls of the returned function ends, the argument
// function is triggered. The end of a sequence is defined by the `wait`
// parameter. If `immediate` is passed, the argument function will be
// triggered at the beginning of the sequence instead of at the end.
function debounce(func, wait, immediate) {
var timeout, previous, args, result, context;
var later = function() {
var passed = now() - previous;
if (wait > passed) {
timeout = setTimeout(later, wait - passed);
} else {
timeout = null;
if (!immediate) result = func.apply(context, args);
// This check is needed because `func` can recursively invoke `debounced`.
if (!timeout) args = context = null;
}
};
var debounced = restArguments(function(_args) {
context = this;
args = _args;
previous = now();
if (!timeout) {
timeout = setTimeout(later, wait);
if (immediate) result = func.apply(context, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = args = context = null;
};
return debounced;
}
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var _now = now();
if (!previous && options.leading === false) previous = _now;
var remaining = wait - (_now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = _now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}