使用React Input的Lodash防抖函数

47

我正在尝试在一个由输入 onChange 事件调用的搜索函数中使用 lodash 实现防抖动。下面的代码生成了一个类型错误“预期为函数”,我理解这是因为 lodash 正在期望一个函数。正确的做法是什么,能否在一行内完成?我已经尝试了 SO 上迄今为止的几乎每个示例,但都没有成功。

search(e){
 let str = e.target.value;
 debounce(this.props.relay.setVariables({ query: str }), 500);
},

尝试一下这个 https://www.freecodecamp.org/news/debounce-and-throttle-in-react-with-hooks/ - U.A
13个回答

69
使用功能性的React组件时,尝试使用useCallbackuseCallback会将你的防抖函数进行记忆化,这样当组件重新渲染时就不会一次又一次地重新创建防抖函数。如果没有使用useCallback,防抖函数将无法与下一个按键同步。
import {useCallback} from 'react';
import _debounce from 'lodash/debounce';
import axios from 'axios';

function Input() {
    const [value, setValue] = useState('');

    const debounceFn = useCallback(_debounce(handleDebounceFn, 1000), []);

    function handleDebounceFn(inputValue) {
        axios.post('/endpoint', {
          value: inputValue,
        }).then((res) => {
          console.log(res.data);
        });
    }


    function handleChange (event) {
        setValue(event.target.value);
        debounceFn(event.target.value);
    };

    return <input value={value} onChange={handleChange} />
}

你的导入有一个拼写错误:import _debouce from 'lodash/debounce';它不是_debouce,而是_debounce。 - Juanma Menendez
谢谢您,朱尔斯! - Ashfaq nisar
只是一个警告,useCallback会将所有使用useState的变量设置为页面加载时的初始值。我遇到了这个错误,现在正在尝试找到另一种使用防抖器的方法。 - Fiddle Freak
@FiddleFreak 我从未遇到过这种情况。我认为问题是其他方面引起的。 - Jules Patry
@JulesPatry 我通过这种方式解决了那个问题 > https://dev59.com/v20NtIcB2Jgan1znQ_hA - Fiddle Freak
确保将处理程序作为函数调用,而不是赋值给常量handleDebounceFn = (),因为它不会将变量提升到声明之上,但对于显式函数则会。 - Kai Durai

43

防抖函数可以在JSX中内联传递,也可以直接设置为类方法,如下所示:

search: _.debounce(function(e) {
  console.log('Debounced Event:', e);
}, 1000)

小提琴(Fiddle):https://jsfiddle.net/woodenconsulting/69z2wepo/36453/

如果您使用es2015+,您可以在constructor或生命周期方法(例如componentWillMount)中直接定义您的防抖函数。

示例:

class DebounceSamples extends React.Component {
  constructor(props) {
    super(props);

    // Method defined in constructor, alternatively could be in another lifecycle method
    // like componentWillMount
    this.search = _.debounce(e => {
      console.log('Debounced Event:', e);
    }, 1000);
  }

  // Define the method directly in your class
  search = _.debounce((e) => {
    console.log('Debounced Event:', e);
  }, 1000)
}

2
谢谢。我现在看到的是合成事件的控制台日志,我需要使用e.target.value来执行搜索。我尝试过e.persist(),但似乎没起作用。技术上防抖已经起作用,但如果没有传递值,它就无法工作。感谢任何帮助。 - Michael Kaufman
我不能完全使用那个,但它帮我达到了我想要的目标。基本上,我使用了 input 调用了 search(e),然后将该事件传递给另一个具有去抖动功能的函数。我读到了 event.persist(),但我无法让它生效。谢谢你的帮助! - Michael Kaufman
@Jeff 木制的 fidden 已经损坏了。 - ey dee ey em
感谢您建议使用componentWillMount。在防抖函数中也能够访问props函数。如果我将其放在构造函数中,不知何故无法访问props函数。 - Rajesh Mbm
@RajeshMbm 你可以在类构造函数内部访问props,参见更新后的示例 - 它作为第一个参数可用(请确保包含super调用)。 - Jeff Wooden
如果我们能够看到一个使用React Hooks的解决方案,那就太好了。 - Fiddle Freak

22
这是我在整整一天的谷歌搜索后必须完成的步骤。
const MyComponent = (props) => {
  const [reload, setReload] = useState(false);

  useEffect(() => {
    if(reload) { /* Call API here */ }
  }, [reload]);

  const callApi = () => { setReload(true) }; // You might be able to call API directly here, I haven't tried
  const [debouncedCallApi] = useState(() => _.debounce(callApi, 1000));

  function handleChange() { 
    debouncedCallApi(); 
  }

  return (<>
    <input onChange={handleChange} />
  </>);
}

useEffect 只会触发一次,因为第一次调用后的重新加载(reload)始终为真(true)。 - CuteShaun
尝试将你的值设置为 handleChange,然后是 debouncedCallApi,再接着是 callApi -> state,最后在 useEffect 中触发你的函数 ^_^ - CuteShaun

4

改进这个答案:https://dev59.com/rFoV5IYBdhLWcg3wZOGe#67941248

使用 useCallbackdebounce 已知会导致 eslint 发出 exhaustive deps 警告。

以下是如何在函数组件中使用 useMemo 实现:

import { useMemo } from 'react';
import { debounce } from 'lodash';
import axios from 'axios';

function Input() {
    const [value, setValue] = useState('');

    const debounceFn = useMemo(() => debounce(handleDebounceFn, 1000), []);

    function handleDebounceFn(inputValue) {
        axios.post('/endpoint', {
          value: inputValue,
        }).then((res) => {
          console.log(res.data);
        });
    }


    function handleChange (event) {
        setValue(event.target.value);
        debounceFn(event.target.value);
    };

    return <input value={value} onChange={handleChange} />
}

我们正在使用useMemo来返回一个记忆化的值,其中这个值是由debounce返回的函数。

4
很多答案我发现过于复杂或不准确(即没有真正的去抖动)。这里提供一个简单明了的解决方案,并进行检查:
const [count, setCount] = useState(0); // simple check debounce is working
const handleChangeWithDebounce = _.debounce(async (e) => {
    if (e.target.value && e.target.value.length > 4) {
        // TODO: make API call here
        setCount(count + 1);
        console.log('the current count:', count)
    }
}, 1000);

<input onChange={handleChangeWithDebounce}></input>

3

这并不是一个简单的问题。

一方面,为了解决你所遇到的错误,你需要将你的setVariables封装在函数中:

 search(e){
  let str = e.target.value;
  _.debounce(() => this.props.relay.setVariables({ query: str }), 500);
}

另一方面,我认为去抖逻辑必须封装在Relay内部。


1
一些答案忽略了一个事实,如果你想使用事件对象(e)中的类似e.target.value的值,在通过防抖函数时原始事件值将会变为null。看看这个错误信息:警告:出于性能原因,此合成事件被重用。如果您看到此消息,则正在访问已释放/无效的合成事件上的属性nativeEvent。这被设置为null。如果您必须保留原始合成事件,请使用event.persist()。正如消息所说,你必须在你的事件函数中包含e.persist()。例如:

const onScroll={(e) => {
  debounceFn(e);
  e.persist();
}}

当然,你的 debounceFn 需要在 return 语句之外进行作用域限定,以便利用 React.useCallback(),这是必要的。我的 debounceFn 如下所示:

const debounceFn = React.useCallback(
  _.debounce((e) => 
      calculatePagination(e), 
      500, {
            trailing: true,
      }
  ),
  []
);


1
我建议不要在防抖函数中使用useCallback。这会强制所有的useState钩子重置为页面加载时的初始状态。 - Fiddle Freak

0

@Aximili

const [debouncedCallApi] = useState(() => _.debounce(callApi, 1000));

看起来有点奇怪 :) 我更喜欢使用useCallback的解决方案:

const [searchFor, setSearchFor] = useState('');

const changeSearchFor = debounce(setSearchFor, 1000);
const handleChange = useCallback(changeSearchFor, []);

0

这是正确的FC方法 @

Aximili只会触发一次

import { SyntheticEvent } from "react"

export type WithOnChange<T = string> = {
    onChange: (value: T) => void
}

export type WithValue<T = string> = {
    value: T
}

//  WithValue & WithOnChange
export type VandC<A = string> = WithValue<A> & WithOnChange<A>

export const inputValue = (e: SyntheticEvent<HTMLElement & { value: string }>): string => (e.target as HTMLElement & { value: string }).value

const MyComponent: FC<VandC<string>> = ({ onChange, value }) => {
    const [reload, setReload] = useState(false)
    const [state, setstate] = useState(value)
    useEffect(() => {
        if (reload) {
            console.log('called api ')
            onChange(state)
            setReload(false)
        }
    }, [reload])

    const callApi = () => {

        setReload(true)
    } // You might be able to call API directly here, I haven't tried
    const [debouncedCallApi] = useState(() => _.debounce(callApi, 1000))

    function handleChange(x:string) {
        setstate(x)
        debouncedCallApi()
    }

    return (<>
        <input
            value={state} onChange={_.flow(inputValue, handleChange)} />
    </>)
}



0

对于您的情况,应该是:

search = _.debounce((e){
 let str = e.target.value;
 this.props.relay.setVariables({ query: str });
}, 500),

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接