web-dev-qa-db-fra.com

Comment utiliser l'accélérateur ou l'anti-rebond avec React Hook?

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?

36
Alexandre Annic

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.

45
skyboyer

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.

2
Mehdi Dehghani

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}
  );
}
0
Saman Mohamadi

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 />
}
0
hossein alipour

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,
  };
}
0
user1730335

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>;
};
0
Nelu