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 为组件设置本地状态。
然后在 input
的 onChange
函数中更新状态,然后尝试调用网络服务端点以使用 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.throttle
和 lodash.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} />
}
最好先看看我的代码:
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 为组件设置本地状态。
然后在 input
的 onChange
函数中更新状态,然后尝试调用网络服务端点以使用 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 freshsetTimeout
callback. So we will useuseRef()
hook as value returned byuseRef()
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.throttle
和 lodash.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} />
}