Lodash debounce 在 React 中不起作用

Lodash debounce not working in React

最好先看看我的代码:

import React, { Component } from 'react';
import _ from 'lodash';
import Services from 'Services'; // Webservice calls

export default class componentName extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: this.props.value || null
    }
  }

  onChange(value) {
    this.setState({ value });

    // This doesn't call Services.setValue at all
    _.debounce(() => Services.setValue(value), 1000);
  }

  render() {
    return (
      <div>
        <input 
          onChange={(event, value) => this.onChange(value)}
          value={this.state.value}
        />
      </div>
    )
  }
}

只是一个简单的输入。在构造函数中,它从属性(如果可用)处获取 value at 为组件设置本地状态。

然后在 inputonChange 函数中更新状态,然后尝试调用网络服务端点以使用 Services.setValue() 设置新值。

如果我直接通过输入的 onChange 设置 debounce 是有效的,如下所示:

<input 
  value={this.state.value} 
  onChange={_.debounce((event, value) => this.onChange(value), 1000)} 
/>

但随后 this.setState 仅每 1000 毫秒调用一次并更新视图。所以在文本字段中输入最终看起来很奇怪,因为你输入的内容只在一秒钟后显示。

遇到这种情况我该怎么办?

出现问题是因为你没有调用debounce函数,你可以通过以下方式

export default class componentName extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: this.props.value || null
    }
    this.servicesValue = _.debounce(this.servicesValue, 1000);
  }

  onChange(value) {
    this.setState({ value });
    this.servicesValue(value);
  }
  servicesValue = (value) => {
      Services.setValue(value)
  }
  render() {
    return (
      <div>
        <input 
          onChange={(event, value) => this.onChange(value)}
          value={this.state.value}
        />
      </div>
    )
  }
}

对于那些因为油门/去抖不适用于 FunctionComponent 而来到这里的人的解决方案 - 你需要通过 useRef():

存储去抖函数
export const ComponentName = (value = null) => {
  const [inputValue, setInputValue] = useState(value);

  const setServicesValue = value => Services.setValue(value);

  const setServicesValueDebounced = useRef(_.debounce(setServicesValue, 1000));

  const handleChange = ({ currentTarget: { value } }) => {
    setInputValue(value);
    setServicesValueDebounced.current(value);
  };

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

This medium article 完美解释了发生的事情:

Local variables inside a function expires after every call. Every time the component is re-evaluated, the local variables gets initialized again. Throttle and debounce works using window.setTimeout() behind the scenes. Every time the function component is evaluated, you are registering a fresh setTimeout callback. So we will use useRef() hook as value returned by useRef() does not get re-evaluated every time the functional component is executed. The only inconvenience is that you have to access your stored value via the .current property.

我创建了 sandbox,其中包含微型 lodash.throttlelodash.debounce 包,因此您可以对两者进行试验并选择合适的行为

对于 React 函数式组件,debounce 默认不工作。您必须执行以下操作才能使其正常工作:

const debouncedFunction= React.useCallback(debounce(functionToCall, 400), []);

useCallback 利用 debounce 返回的函数并按预期工作。 虽然,当您想在去抖动函数中使用状态变量时,这有点复杂(通常是这种情况)。

React.useCallback(debounce(fn, timeInMs), [])

React.useCallback 的第二个参数用于依赖项。如果你想在 debounced 函数中使用 state 或 prop 变量,默认情况下,它使用旧版本的 state 变量,这将导致你的函数使用变量的历史值,这不是你需要的。 要解决此问题,您必须像在 React.useEffect 中那样包含状态变量,如下所示:

React.useCallback(debounce(fn, timeInMs), [stateVariable1, stateVariable2])

此实现可能会解决您的目的。但是您会注意到,每次作为依赖项更改传递的状态变量(stateVariable1、stateVariable2)都会调用 debounced 函数。这可能不是您所需要的,尤其是在使用输入字段等受控组件时。

我意识到的最佳解决方案是花一些时间将功能组件更改为基于 class 的组件并使用以下实现:

constructor(props)
    {
        super();
        this.state = {...};
        this.functionToCall= debounce(this.functionToCall.bind(this), 400, {'leading': true});
    }

我为那些正在使用 React 功能组件的人写了一个钩子。

It's typescript, but you can ignore type annotations to use on your javascript application.

| use-debounce.ts |

import { debounce, DebounceSettings } from 'lodash'
import { useRef } from 'react'

interface DebouncedArgs<T> {
  delay?: number
  callback?: (value: T) => void
  debounceSettings?: DebounceSettings
}



export const useDebounce = <T = unknown>({ callback, debounceSettings, delay = 700 }: DebouncedArgs<T>) => {
  const dispatchValue = (value: T) => callback?.(value)

  const setValueDebounced = useRef(debounce(dispatchValue, delay, { ...debounceSettings, maxWait: debounceSettings?.maxWait || 1400 }))

  return (value: T) => setValueDebounced.current(value)
}

|用法:|

export const MyInput: FC = () => {
  const [value, setValue] = useState<string>('')
  const debounce = useDebounce({ callback: onChange })

  const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
    const { value } = evt.currentTarget
    setValue(value)
    debounce(value)
  }

  function onChange(value: string) {
    // send request to the server for example
    console.log(value)
  }

  return <input value={value} onInput={handleOnInput} />
}