J'essaie d'utiliser la méthode throttle
de lodash dans un composant fonctionnel, par exemple:
const App = () => {
const [value, setValue] = useState(0)
useEffect(throttle(() => console.log(value), 1000), [value])
return (
<button onClick={() => setValue(value + 1)}>{value}</button>
)
}
Étant donné que la méthode à l'intérieur de useEffect
est redéclarée à chaque rendu, l'effet de limitation ne fonctionne pas.
Quelqu'un at-il une solution simple?
Après un certain temps, je suis sûr qu'il est beaucoup plus facile de gérer les choses par vous-même avec setTimeout/clearTimeout
(et le déplacer dans un crochet personnalisé séparé) que de travailler avec des assistants fonctionnels. Le traitement ultérieur crée des défis supplémentaires juste après avoir appliqué cela à useCallback
qui peut être recréé en raison d'un changement de dépendance, mais nous ne voulons pas réinitialiser le délai d'exécution.
réponse originale ci-dessous
vous pouvez (et probablement besoin) useRef
pour stocker la valeur entre les rendus. Tout comme c'est suggéré pour les minuteries
Quelque chose comme ca
const App = () => {
const [value, setValue] = useState(0)
const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))
useEffect(() => throttled.current(value), [value])
return (
<button onClick={() => setValue(value + 1)}>{value}</button>
)
}
Comme pour useCallback
:
Cela peut aussi fonctionner
const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);
Mais si nous essayons de recréer le rappel une fois que value
est changé:
const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);
nous pouvons constater que cela ne retarde pas l'exécution: une fois que value
est modifié, le rappel est immédiatement recréé et exécuté.
Je vois donc useCallback
en cas de retard d'exécution ne fournit pas d'avantage significatif. C'est à vous.
[UPD] au départ c'était
const throttled = useRef(throttle(() => console.log(value), 1000))
useEffect(throttled.current, [value])
mais de cette façon throttled.current
s'est lié à l'initiale value
(sur 0) par fermeture. Il n'a donc jamais été modifié, même lors des prochains rendus.
Soyez donc prudent lorsque vous poussez des fonctions dans useRef
à cause de la fonction de fermeture.
Il pourrait s'agir de minuscules crochets personnalisés, comme celui-ci:
useDebounce.js
import React, { useState, useEffect } from 'react';
export default (value, timeout) => {
const [state, setState] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setState(value), timeout);
return () => clearTimeout(handler);
}, [value]);
return state;
}
Exemple d'utilisation:
import React, { useEffect } from 'react';
import useDebounce from '/path/to/useDebounce';
const App = (props) => {
const [state, setState] = useState({title: ''});
const debouncedTitle = useDebounce(state.title, 1000);
useEffect(() => {
// do whatever you want with state.title/debouncedTitle
}, [debouncedTitle]);
return (
// ...
);
}
// ...
Mise à jour: Comme vous le savez probablement, useEffect
s'exécute toujours sur le rendu initial, et à cause de cela si vous utilisez ma réponse, vous aurez probablement voir le rendu de votre composant s'exécute deux fois, ne vous inquiétez pas, il vous suffit d'écrire un autre crochet personnalisé. consultez mon autre réponse pour plus d'informations.
J'ai écrit deux crochets simples ( se-throttled-effect et se-debounce-effect ) pour ce cas d'utilisation peut-être qu'il sera utile pour quelqu'un d'autre à la recherche d'une solution simple.
import React, { useState } from 'react';
import useThrottledEffect from 'use-throttled-effect';
export default function Input() {
const [count, setCount] = useState(0);
useEffect(()=>{
const interval = setInterval(() => setCount(count=>count+1) ,100);
return ()=>clearInterval(interval);
},[])
useThrottledEffect(()=>{
console.log(count);
}, 1000 ,[count]);
return (
{count}
);
}
J'utilise quelque chose comme ça et ça marche très bien:
let debouncer = debounce(
f => f(),
1000,
{ leading: true }, // debounce one on leading and one on trailing
);
function App(){
let [state, setState] = useState();
useEffect(() => debouncer(()=>{
// you can use state here for new state value
}),[state])
return <div />
}
Si vous l'utilisez dans le gestionnaire, je suis assez certain que c'est la façon de le faire.
function useThrottleScroll() {
const savedHandler = useRef();
function handleEvent() {}
useEffect(() => {
savedHandleEvent.current = handleEvent;
}, []);
const throttleOnScroll = useRef(throttle((event) => savedHandleEvent.current(event), 100)).current;
function handleEventPersistence(event) {
return throttleOnScroll(event);
}
return {
onScroll: handleEventPersistence,
};
}
Dans mon cas, j'avais également besoin de passer l'événement. Je suis allé avec ça:
const MyComponent = () => {
const handleScroll = useMemo(() => {
const throttled = throttle(e => console.log(e.target.scrollLeft), 300);
return e => {
e.persist();
return throttled(e);
};
}, []);
return <div onScroll={handleScroll}>Content</div>;
};