Lodash debounce 在 React 中不起作用

Posted

技术标签:

【中文标题】Lodash debounce 在 React 中不起作用【英文标题】:Lodash debounce not working in React 【发布时间】:2018-05-28 07:48:24 【问题描述】:

最好先看看我的代码:

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,为组件设置本地状态。

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

如果我像这样直接通过输入的onChange 设置debounce 会起作用:

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

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

遇到这种情况该怎么办?

【问题讨论】:

【参考方案1】:

出现问题是因为你没有调用去抖动函数,你可以通过以下方式来做

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>
    )
  

【讨论】:

我明白了!我也可以将 _.debounce(...) 包装在 onChange 中的自执行匿名函数中,对吗? 是的,你也可以这样做,但是,在这种情况下,你每次都会返回一个新的去抖动函数,所以上述方法比你建议的要好【参考方案2】:

针对因油门/去抖不适用于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 完美地解释了发生了什么:

函数内的局部变量在每次调用后都会过期。每次 重新评估组件,初始化局部变量 再次。使用window.setTimeout() 进行油门和去抖动工作 场景。每次评估功能组件时,您都是 注册一个新的setTimeout 回调。 所以我们将使用useRef() 钩子作为useRef() 返回的值,不会在每次执行功能组件时重新评估。唯一的不便是您必须通过.current 属性访问您的储值。

我已经创建了 sandbox 与微小的 lodash.throttlelodash.debounce 包,所以你可以试验两者并选择合适的行为

【讨论】:

感谢您的回答。我有一个带有受控输入的功能组件,它可以更新状态并通过服务设置值,而您的沙箱确实可以做到这一点。也就是说,我发现 useCallback 更合适,正如那篇 Medium 文章的更新所示。 @KevinAshworth 很高兴它有帮助!【参考方案3】:

对于 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 函数。这可能不是您所需要的,尤其是在使用输入字段等受控组件时。

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

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

【讨论】:

谢谢,@SummerFort ``` useCallback(debounce(fn, timeInMs), []) ``` 正是我需要的!

以上是关于Lodash debounce 在 React 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

react函数式组件中使用lodash的debounce

使用 Lodash debounce 和 React useCallback 输入 onChange 事件

react 中使用 lodash 中的 _.throttle

使用 lodash.debounce 延迟测试组件失败

Lodash 之 debounce

lodash源码学习debounce,throttle