先说结论 ,ahook是更好的选择


继之前的debounce,和throttle的js实现,然后看了loadash和underscroe的源码,改成typescript并整合到自己的utils库里。

yarn add @kacoro/utils


然后接着用react中使用,

import {throttle,debounce} from '@kacoro/utils'

<input type="text" onChange={(e)=>{
e.persist()   // react默认会清除所有的默认属性,所以需要添加这段,保留参数的属性
debounce(()=>{
    console.log(e.target.value
) },500)()
}} />


在hook来实现。参考:https://www.freecodecamp.org/news/debounce-and-throttle-in-react-with-hooks/

通过使用useRef、和useCallback来实现。


使用useCallback实现bebounce

在向子组件传递回调时,useCallback通常用于性能优化。但我们可以使用它的约束记忆一个回调函数,

,以确保该debouncedSave引用同去抖功能跨越渲染。

   const [value, setValue] = useState('');
  const [dbValue, saveToDb] = useState(''); // would be an API call normally
  
  const debouncedSaveByCallBack:Function = useCallback(debounce((nextValue: string) => saveToDb(nextValue), 1000),[],); // will be created only once initially
  
  const handleDbChange = (event: { target: { value: any; }; }) => {
    const { value: nextValue } = event.target;
    setValue(nextValue);
     debouncedSaveByCallBack(nextValue);
  };

 

使用useRef实现throttle

useRef为我们提供了一个可变对象,其current属性引用传递的初始值。 如果我们不手动更改它,则该值将在组件的整个生命周期内保持不变 这类似于类实例的属性

  const [value2, setValue2] = useState('');
  const [thValue, saveToTh] = useState(''); // would be an API call normally
  const throttledSaveByRef:Function = useRef(throttle((nextValue: string) => saveToTh(nextValue), 1000))
  .current;
  
  const handleThChange = (event: { target: { value: any; }; }) => {
    const { value: nextValue } = event.target;
    setValue2(nextValue);
    throttledSaveByRef(nextValue);
  };


自定义hooks

interface CurrentRef {
  fn: Function;
  timer?: ReturnType<typeof setTimeout> | null
}

function useDebounce(fn: Function, delay: number = 300, dep = []) {
  const { current } = useRef<CurrentRef>({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;  // eslint-disable-next-line 
  }, [fn]);

  return useCallback(function f(...args: any[]) {
    if (current.timer) {
      clearTimeout(current.timer);
    }
    current.timer = setTimeout(() => {
      current.fn.call(useDebounce, ...args);
    }, delay); // eslint-disable-next-line 
  }, dep)
}

function useThrottle(fn: Function, delay: number = 300, dep = []) {
  const { current } = useRef<CurrentRef>({ fn, timer: null });
  useEffect(function () {
    current.fn = fn; // eslint-disable-next-line
  }, [fn]);

  return useCallback(function f(...args) {
    if (!current.timer) {
      current.timer = setTimeout(() => {
        delete current.timer; // eslint-disable-next-line
      }, delay);
      current.fn.call(useThrottle, ...args);
    }// eslint-disable-next-line
  }, dep);
}


 const [counter, setCounter] = useState(0);
  const handleDebounceClick = useDebounce(function () {
    setCounter(counter + 1)
  }, 300)
  const handleThrottleClick = useThrottle(function () {
    setCounter(counter + 1)
  }, 300)


完整代码

import React, { useCallback, useEffect, useRef, useState } from 'react';

import {throttle, debounce } from '@kacoro/utils'

interface CurrentRef {
  fn: Function;
  timer?: ReturnType<typeof setTimeout> | null
}

function useDebounce(fn: Function, delay: number = 300, dep = []) {
  const { current } = useRef<CurrentRef>({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;  // eslint-disable-next-line 
  }, [fn]);

  return useCallback(function f(...args: any[]) {
    if (current.timer) {
      clearTimeout(current.timer);
    }
    current.timer = setTimeout(() => {
      current.fn.call(useDebounce, ...args);
    }, delay); // eslint-disable-next-line 
  }, dep)
}

function useThrottle(fn: Function, delay: number = 300, dep = []) {
  const { current } = useRef<CurrentRef>({ fn, timer: null });
  useEffect(function () {
    current.fn = fn; // eslint-disable-next-line
  }, [fn]);

  return useCallback(function f(...args) {
    if (!current.timer) {
      current.timer = setTimeout(() => {
        delete current.timer; // eslint-disable-next-line
      }, delay);
      current.fn.call(useThrottle, ...args);
    }// eslint-disable-next-line
  }, dep);
}
function DebonceThrottle() {
  const [counter, setCounter] = useState(0);
  const handleDebounceClick = useDebounce(function () {
    setCounter(counter + 1)
  }, 300)
  const handleThrottleClick = useThrottle(function () {
    setCounter(counter + 1)
  }, 300)

  const [value, setValue] = useState('');
  const [dbValue, saveToDb] = useState(''); // would be an API call normally
  
  const debouncedSaveByCallBack:Function = useCallback(debounce((nextValue: string) => saveToDb(nextValue), 1000),[],); // will be created only once initially
  
  const handleDbChange = (event: { target: { value: any; }; }) => {
    const { value: nextValue } = event.target;
    setValue(nextValue);
     debouncedSaveByCallBack(nextValue);
  };
 
  const [value2, setValue2] = useState('');
  const [thValue, saveToTh] = useState(''); // would be an API call normally
  const throttledSaveByRef:Function = useRef(throttle((nextValue: string) => saveToTh(nextValue), 1000))
  .current;
  
  const handleThChange = (event: { target: { value: any; }; }) => {
    const { value: nextValue } = event.target;
    setValue2(nextValue);
    throttledSaveByRef(nextValue);
  };
 
  return (
    <div>
      <textarea value={value} onChange={handleDbChange} rows={5} cols={50} />
      <section >
        <div>
          <b>Editor (Client):</b>
          {value}
        </div>
        <div>
          <b>Saved (Debounce):</b>
          {dbValue}
        </div>
      </section>
      <textarea value={value2} onChange={handleThChange} rows={5} cols={50} />
      <section >
        <div>
          <b>Editor (Client):</b>
          {value2}
        </div>
        <div>
          <b>Saved (Throttle):</b>
          {thValue}
        </div>
      </section>
      <section>
        <button
          onClick={handleDebounceClick}
        >click debounce</button>  <button
          onClick={handleThrottleClick}
        >click throttle</button>
        <div>{counter}</div>
      </section>
    </div>
  );
}

export default DebonceThrottle;
0条评论