How to use throttle or debounce with React Hook?

后端 未结 17 1006
甜味超标
甜味超标 2020-11-30 04:27

I\'m trying to use the throttle method from lodash in a functional component, e.g.:

const App = () => {
  const [value, setValue         


        
17条回答
  •  陌清茗
    陌清茗 (楼主)
    2020-11-30 05:03

    useThrottle , useDebounce

    How to use both

    const App = () => {
      const [value, setValue] = useState(0);
      // called at most once per second (same API with useDebounce)
      const throttledCb = useThrottle(() => console.log(value), 1000);
      // usage with useEffect: invoke throttledCb on value change
      useEffect(throttledCb, [value]);
      // usage as event handler
      
      // ... other render code
    };
    

    useThrottle (Lodash)

    import _ from "lodash"
    
    function useThrottle(cb, delay) {
      const options = { leading: true, trailing: false }; // add custom lodash options
      const cbRef = useRef(cb);
      // use mutable ref to make useCallback/throttle not depend on `cb` dep
      useEffect(() => { cbRef.current = cb; });
      return useCallback(
        _.throttle((...args) => cbRef.current(...args), delay, options),
        [delay]
      );
    }
    

    const App = () => {
      const [value, setValue] = useState(0);
      const invokeDebounced = useThrottle(
        () => console.log("changed throttled value:", value),
        1000
      );
      useEffect(invokeDebounced, [value]);
      return (
        

    value will be logged at most once per second.

    ); }; function useThrottle(cb, delay) { const options = { leading: true, trailing: false }; // pass custom lodash options const cbRef = useRef(cb); useEffect(() => { cbRef.current = cb; }); return useCallback( _.throttle((...args) => cbRef.current(...args), delay, options), [delay] ); } ReactDOM.render(, document.getElementById("root"));
    
    
    
    
    

    useDebounce (Lodash)

    import _ from "lodash"
    
    function useDebounce(cb, delay) {
      // ...
      const inputsRef = useRef(cb); // mutable ref like with useThrottle
      useEffect(() => { inputsRef.current = { cb, delay }; }); //also track cur. delay
      return useCallback(
        _.debounce((...args) => {
            // Debounce is an async callback. Cancel it, if in the meanwhile
            // (1) component has been unmounted (see isMounted in snippet)
            // (2) delay has changed
            if (inputsRef.current.delay === delay && isMounted())
              inputsRef.current.cb(...args);
          }, delay, options
        ),
        [delay, _.debounce]
      );
    }
    

    const App = () => {
      const [value, setValue] = useState(0);
      const invokeDebounced = useDebounce(
        () => console.log("debounced", value),
        1000
      );
      useEffect(invokeDebounced, [value]);
      return (
        

    Logging is delayed until after 1 sec. has elapsed since the last invocation.

    ); }; function useDebounce(cb, delay) { const options = { leading: false, trailing: true }; const inputsRef = useRef(cb); const isMounted = useIsMounted(); useEffect(() => { inputsRef.current = { cb, delay }; }); return useCallback( _.debounce( (...args) => { // Don't execute callback, if (1) component in the meanwhile // has been unmounted or (2) delay has changed if (inputsRef.current.delay === delay && isMounted()) inputsRef.current.cb(...args); }, delay, options ), [delay, _.debounce] ); } function useIsMounted() { const isMountedRef = useRef(true); useEffect(() => { return () => { isMountedRef.current = false; }; }, []); return () => isMountedRef.current; } ReactDOM.render(, document.getElementById("root"));
    
    
    
    
    


    Customizations

    1. You might replace Lodash with your own throttle or debounce code, like:

    const debounceImpl = (cb, delay) => {
      let isDebounced = null;
      return (...args) => {
        clearTimeout(isDebounced);
        isDebounced = setTimeout(() => cb(...args), delay);
      };
    };
    
    const throttleImpl = (cb, delay) => {
      let isThrottled = false;
      return (...args) => {
        if (isThrottled) return;
        isThrottled = true;
        cb(...args);
        setTimeout(() => {
          isThrottled = false;
        }, delay);
      };
    };
    
    const App = () => {
      const [value, setValue] = useState(0);
      const invokeThrottled = useThrottle(
        () => console.log("throttled", value),
        1000
      );
      const invokeDebounced = useDebounce(
        () => console.log("debounced", value),
        1000
      );
      useEffect(invokeThrottled, [value]);
      useEffect(invokeDebounced, [value]);
      return ;
    };
    
    function useThrottle(cb, delay) {
      const cbRef = useRef(cb);
      useEffect(() => {
        cbRef.current = cb;
      });
      return useCallback(
        throttleImpl((...args) => cbRef.current(...args), delay),
        [delay]
      );
    }
    
    function useDebounce(cb, delay) {
      const cbRef = useRef(cb);
      useEffect(() => {
        cbRef.current = cb;
      });
      return useCallback(
        debounceImpl((...args) => cbRef.current(...args), delay),
        [delay]
      );
    }
    
    ReactDOM.render(, document.getElementById("root"));
    
    
    
    

    2. useThrottle can be shortened up, if always used with useEffect (same for useDebounce):

    const App = () => {
      // useEffect now is contained inside useThrottle
      useThrottle(() => console.log(value), 1000, [value]);
      // ...
    };
    

    const App = () => {
      const [value, setValue] = useState(0);
      useThrottle(() => console.log(value), 1000, [value]);
      return (
        

    value will be logged at most once per second.

    ); }; function useThrottle(cb, delay, additionalDeps) { const options = { leading: true, trailing: false }; // pass custom lodash options const cbRef = useRef(cb); const throttledCb = useCallback( _.throttle((...args) => cbRef.current(...args), delay, options), [delay] ); useEffect(() => { cbRef.current = cb; }); // set additionalDeps to execute effect, when other values change (not only on delay change) useEffect(throttledCb, [throttledCb, ...additionalDeps]); } ReactDOM.render(, document.getElementById("root"));
    
    
    
    
    

提交回复
热议问题