Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useState, useEffect, useRef, useCallback } from "react";
/**
* useThrottledValue
* Retorna uma cópia com throttle do `value` que atualiza no máximo
* uma vez a cada `interval` ms.
*/
function useThrottledValue<T>(value: T, interval: number): T {
const [throttled, setThrottled] = useState(value);
const lastUpdated = useRef(Date.now());
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
const now = Date.now();
const elapsed = now - lastUpdated.current;
if (elapsed >= interval) {
setThrottled(value);
lastUpdated.current = now;
} else {
// Agenda uma atualização de cauda
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setThrottled(value);
lastUpdated.current = Date.now();
}, interval - elapsed);
}
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, [value, interval]);
return throttled;
}
/**
* useThrottledCallback
* Retorna uma versão estável e com throttle do `callback` que
* executa no máximo uma vez a cada `interval` ms.
*/
function useThrottledCallback<T extends (...args: any[]) => void>(
callback: T,
interval: number
): T & { cancel: () => void } {
const callbackRef = useRef(callback);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastCalledRef = useRef(0);
const lastArgsRef = useRef<Parameters<T> | null>(null);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, []);
const cancel = useCallback(() => {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = null;
lastArgsRef.current = null;
}, []);
const throttled = useCallback(
(...args: Parameters<T>) => {
lastArgsRef.current = args;
const now = Date.now();
const elapsed = now - lastCalledRef.current;
if (elapsed >= interval) {
callbackRef.current(...args);
lastCalledRef.current = now;
} else if (!timerRef.current) {
timerRef.current = setTimeout(() => {
if (lastArgsRef.current) {
callbackRef.current(...lastArgsRef.current);
}
lastCalledRef.current = Date.now();
timerRef.current = null;
lastArgsRef.current = null;
}, interval - elapsed);
}
},
[interval]
) as T & { cancel: () => void };
throttled.cancel = cancel;
return throttled;
}Quando usar isso: Você precisa de atualizações consistentes e espaçadas durante eventos contínuos como rolagem, redimensionamento ou movimento do mouse, em vez de esperar o evento parar (isso seria debounce).
"use client";
import { useState, useEffect, useRef } from "react";
function ScrollTracker() {
const [scrollY, setScrollY] = useState(0);
const handleScroll = useThrottledCallback(() => {
setScrollY(window.scrollY);
}, 100);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [handleScroll]);
return (
<div style={{ position: "fixed", top: 10, right: 10 }}>
Scroll: {scrollY}px
</div>
);
}
function ResizeDisplay() {
const [width, setWidth] = useState(
typeof window !== "undefined" ? window.innerWidth : 0
);
const throttledWidth = useThrottledValue(width, 200);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, []);
return (
<p>
Raw: {width}px - Throttled: {throttledWidth}px
</p>
);
}O que isso demonstra:
useThrottledCallback dispara o manipulador de rolagem no máximo a cada 100 ms, mantendo a UI responsiva sem sobrecarregar as atualizações de estado.useThrottledValue suaviza os valores brutos de redimensionamento para atualizar no máximo a cada 200 ms.| Comportamento | Debounce | Throttle |
|---|---|---|
| Quando dispara | Após a entrada parar por N ms | No máximo uma vez a cada N ms |
| Ideal para | Entrada de pesquisa, validação de formulário | Rolagem, redimensionamento, arrastar |
| Responsividade | Parece atrasado | Parece suave |
| Valor de cauda | Apenas o último | Liderança + cauda |
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
value | T | - | O valor a ser limitado |
interval | number | - | Mínimo de ms entre atualizações |
| Retorna | T | - | O valor limitado |
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
callback | (...args) => void | - | Função a ser limitada |
interval | number | - | Mínimo de ms entre chamadas |
| Retorna | T & \{ cancel \} | - | Função limitada com cancelamento |
Throttle apenas de liderança: Pule a chamada de cauda se você quiser apenas o primeiro evento em cada janela. Remova o branch setTimeout de cauda.
Throttle com requestAnimationFrame: Para atualizações visuais, substitua o timer por requestAnimationFrame para um throttle perfeito de 16 ms por frame:
function useRAFCallback(callback: () => void) {
const rafRef = useRef(0);
const callbackRef = useRef(callback);
callbackRef.current = callback;
return useCallback(() => {
cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(() => callbackRef.current());
}, []);
}T no hook de valor preserva o tipo de entrada.Parameters<T> no hook de callback preserva os tipos de argumento.T & \{ cancel \} adiciona métodos de controle sem perder a assinatura original.interval abaixo de 16 ms não oferece benefício, pois o navegador não pode renderizar mais rápido que um frame. Correção: Use um mínimo de 16 ms ou mude para requestAnimationFrame.setTimeout não é perfeitamente preciso. Para código crítico de animação, Correção: use requestAnimationFrame em vez disso.window durante a renderização do servidor lança um erro. Correção: Proteja com typeof window !== "undefined" ou inicialize com um padrão seguro.| Pacote | Nome do Hook | Notas |
|---|---|---|
usehooks-ts | useThrottle | Throttle apenas de valor |
ahooks | useThrottle, useThrottleFn | Completo, configuração de liderança/cauda |
@uidotdev/usehooks | useThrottle | Throttle mínimo de valor |
lodash | _.throttle | Não é um hook; envolva em useRef |
use-debounce | useThrottledCallback | Parte do pacote debounce |
useThrottledValue aceita um valor reativo e retorna uma cópia com throttle que atualiza no máximo uma vez por intervalo.useThrottledCallback aceita uma função e retorna uma versão com throttle dessa função.Armazenar o callback em callbackRef garante que o timeout sempre chame a versão mais recente da função. Sem isso, o closure capturaria um callback desatualizado de um render anterior.
setTimeout também tem um atraso mínimo de ~4 ms na maioria dos navegadores.requestAnimationFrame em vez disso.Chame o método .cancel() na função retornada:
const throttled = useThrottledCallback(handler, 200);
// Mais tarde:
throttled.cancel();Não. Ambos os hooks incluem uma chamada de cauda via setTimeout. Quando o burst termina, o timer de cauda dispara com o valor ou argumentos mais recentes, garantindo que nada seja perdido.
window durante a renderização do lado do servidor lança um ReferenceError.typeof window !== "undefined" antes de ler window.scrollY ou anexar listeners.useEffect no exemplo funcional só é executado no cliente, mas o estado inicial também deve ser seguro.Sem a chamada de cauda, apenas a invocação de liderança dispara. Se o valor mudar durante a janela de cooldown, a atualização final é silenciosamente descartada. Sempre mantenha o branch de cauda para capturar o último valor.
requestAnimationFrame dispara uma vez por frame de exibição (~16 ms a 60 Hz).A assinatura genérica T extends (...args: any[]) => void captura o tipo de função original. Parameters<T> extrai a tupla de argumentos, então o wrapper com throttle aceita os mesmos parâmetros do original.
Ele combina o tipo de função original T com um objeto que tem um método cancel. Isso significa que a função retornada pode ser chamada com a mesma assinatura do original, e também expõe .cancel() para limpeza.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥