如何通过 React Hook 使用节流阀或去抖动?
Posted
技术标签:
【中文标题】如何通过 React Hook 使用节流阀或去抖动?【英文标题】:How to use throttle or debounce with React Hook? 【发布时间】:2019-07-07 01:30:53 【问题描述】:我正在尝试在功能组件中使用来自lodash
的throttle
方法,例如:
const App = () =>
const [value, setValue] = useState(0)
useEffect(throttle(() => console.log(value), 1000), [value])
return (
<button onClick=() => setValue(value + 1)>value</button>
)
由于useEffect
里面的方法在每次渲染时都会重新声明,所以节流效果不起作用。
有人有简单的解决方案吗?
【问题讨论】:
您是否可以选择在App
组件之外定义节流函数并在useEffect
函数中调用它?
是的,我试过了,它可以工作,但就我而言,它不是很优雅,因为我在节流方法中使用了组件变量。
【参考方案1】:
我想使用useState
使用我的节流和去抖动输入加入聚会:
// import useState, useRef from 'react' // nomral import
const useState, useRef = React // inline import
// Throttle
const ThrottledInput = ( onChange, delay = 500 ) =>
const t = useRef()
const handleChange = ( target ) =>
if (!t.current)
t.current = setTimeout(() =>
onChange(target.value)
clearTimeout(t)
t.current = null
, delay)
return (
<input
placeholder="throttle"
onChange=handleChange
/>
)
// Debounce
const DebouncedInput = ( onChange, delay = 500 ) =>
const t = useRef()
const handleChange = ( target ) =>
clearTimeout(t.current)
t.current = setTimeout(() => onChange(target.value), delay)
return (
<input
placeholder="debounce"
onChange=handleChange
/>
)
// ----
ReactDOM.render(<div>
<ThrottledInput onChange=console.log />
<DebouncedInput onChange=console.log />
</div>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
【讨论】:
【参考方案2】:您可以使用useMemo
挂钩来优化您的节流事件处理程序
示例代码如下:
const App = () =>
const [value, setValue] = useState(0);
// ORIGINAL EVENT HANDLER
function eventHandler(event)
setValue(value + 1);
// THROTTLED EVENT HANDLER
const throttledEventHandler = useMemo(() => throttle(eventHandler, 1000), [value]);
return (
<button onClick=throttledEventHandler>Throttled Button with value: value</button>
)
【讨论】:
这个备忘录更新状态,这样可以吗?我想知道 React 的这条指令:“请记住,传递给 useMemo 的函数在渲染期间运行。不要在那里做任何你在渲染时通常不会做的事情。例如,副作用属于 useEffect,而不是 useMemo。” 【参考方案3】:
useThrottle
, useDebounce
如何同时使用
const App = () =>
const [value, setValue] = useState(0);
// called at most once per second (same API with useDebounce)
const throttledCb = useThrottle(() => console.log(value), 1000);
// usage with useEffect: invoke throttledCb on value change
useEffect(throttledCb, [value]);
// usage as event handler
<button onClick=throttledCb>log value</button>
// ... other render code
;
useThrottle
(Lodash)
import _ from "lodash"
function useThrottle(cb, delay)
const options = leading: true, trailing: false ; // add custom lodash options
const cbRef = useRef(cb);
// use mutable ref to make useCallback/throttle not depend on `cb` dep
useEffect(() => cbRef.current = cb; );
return useCallback(
_.throttle((...args) => cbRef.current(...args), delay, options),
[delay]
);
const App = () =>
const [value, setValue] = useState(0);
const invokeDebounced = useThrottle(
() => console.log("changed throttled value:", value),
1000
);
useEffect(invokeDebounced, [value]);
return (
<div>
<button onClick=() => setValue(value + 1)>value</button>
<p>value will be logged at most once per second.</p>
</div>
);
;
function useThrottle(cb, delay)
const options = leading: true, trailing: false ; // pass custom lodash options
const cbRef = useRef(cb);
useEffect(() =>
cbRef.current = cb;
);
return useCallback(
_.throttle((...args) => cbRef.current(...args), delay, options),
[delay]
);
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var useReducer, useEffect, useState, useRef, useCallback = React</script>
<div id="root"></div>
useDebounce
(Lodash)
import _ from "lodash"
function useDebounce(cb, delay)
// ...
const inputsRef = useRef(cb, delay); // mutable ref like with useThrottle
useEffect(() => inputsRef.current = cb, delay ; ); //also track cur. delay
return useCallback(
_.debounce((...args) =>
// Debounce is an async callback. Cancel it, if in the meanwhile
// (1) component has been unmounted (see isMounted in snippet)
// (2) delay has changed
if (inputsRef.current.delay === delay && isMounted())
inputsRef.current.cb(...args);
, delay, options
),
[delay, _.debounce]
);
const App = () =>
const [value, setValue] = useState(0);
const invokeDebounced = useDebounce(
() => console.log("debounced", value),
1000
);
useEffect(invokeDebounced, [value]);
return (
<div>
<button onClick=() => setValue(value + 1)>value</button>
<p> Logging is delayed until after 1 sec. has elapsed since the last invocation.</p>
</div>
);
;
function useDebounce(cb, delay)
const options =
leading: false,
trailing: true
;
const inputsRef = useRef(cb);
const isMounted = useIsMounted();
useEffect(() =>
inputsRef.current = cb, delay ;
);
return useCallback(
_.debounce(
(...args) =>
// Don't execute callback, if (1) component in the meanwhile
// has been unmounted or (2) delay has changed
if (inputsRef.current.delay === delay && isMounted())
inputsRef.current.cb(...args);
,
delay,
options
),
[delay, _.debounce]
);
function useIsMounted()
const isMountedRef = useRef(true);
useEffect(() =>
return () =>
isMountedRef.current = false;
;
, []);
return () => isMountedRef.current;
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var useReducer, useEffect, useState, useRef, useCallback = React</script>
<div id="root"></div>
自定义
1。您可以将 Lodash 替换为您自己的 throttle
或 debounce
代码,例如:
const debounceImpl = (cb, delay) =>
let isDebounced = null;
return (...args) =>
clearTimeout(isDebounced);
isDebounced = setTimeout(() => cb(...args), delay);
;
;
const throttleImpl = (cb, delay) =>
let isThrottled = false;
return (...args) =>
if (isThrottled) return;
isThrottled = true;
cb(...args);
setTimeout(() =>
isThrottled = false;
, delay);
;
;
const App = () =>
const [value, setValue] = useState(0);
const invokeThrottled = useThrottle(
() => console.log("throttled", value),
1000
);
const invokeDebounced = useDebounce(
() => console.log("debounced", value),
1000
);
useEffect(invokeThrottled, [value]);
useEffect(invokeDebounced, [value]);
return <button onClick=() => setValue(value + 1)>value</button>;
;
function useThrottle(cb, delay)
const cbRef = useRef(cb);
useEffect(() =>
cbRef.current = cb;
);
return useCallback(
throttleImpl((...args) => cbRef.current(...args), delay),
[delay]
);
function useDebounce(cb, delay)
const cbRef = useRef(cb);
useEffect(() =>
cbRef.current = cb;
);
return useCallback(
debounceImpl((...args) => cbRef.current(...args), delay),
[delay]
);
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script>var useReducer, useEffect, useState, useRef, useCallback = React</script>
<div id="root"></div>
2。 useThrottle
可以缩短,如果总是与useEffect
一起使用(useDebounce
相同):
const App = () =>
// useEffect now is contained inside useThrottle
useThrottle(() => console.log(value), 1000, [value]);
// ...
;
const App = () =>
const [value, setValue] = useState(0);
useThrottle(() => console.log(value), 1000, [value]);
return (
<div>
<button onClick=() => setValue(value + 1)>value</button>
<p>value will be logged at most once per second.</p>
</div>
);
;
function useThrottle(cb, delay, additionalDeps)
const options = leading: true, trailing: false ; // pass custom lodash options
const cbRef = useRef(cb);
const throttledCb = useCallback(
_.throttle((...args) => cbRef.current(...args), delay, options),
[delay]
);
useEffect(() =>
cbRef.current = cb;
);
// set additionalDeps to execute effect, when other values change (not only on delay change)
useEffect(throttledCb, [throttledCb, ...additionalDeps]);
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var useReducer, useEffect, useState, useRef, useCallback = React</script>
<div id="root"></div>
【讨论】:
为什么要使用useEffect(() => cbRef.current = cb; );
没有任何依赖?这意味着我们在每次重新渲染时运行效果,那么为什么不简单地分配而不使用 useEffect 呢?
好问题 - 这旨在始终包含 cbRef
内的最新回调。可变 ref 可以像 instance variable 一样用于 Hooks - here 是 Overreacted 博客中 setInterval
的一个示例。渲染阶段也应该是纯的,没有副作用,例如兼容 React 并发模式。这就是为什么我们将作业包装在 useEffect
中。
我似乎在使用 useThrottle (Lodash) 时遇到错误:“TypeError: Cannot read property 'apply' of undefined”。再加上,我有一个 ESLint 错误,说“React Hook useCallback 收到了一个依赖项未知的函数。改为传递一个内联函数。”【参考方案4】:
在尝试解决陈旧状态问题时,我刚刚想到了以下模式:
我们可以将 debounced 函数存储在一个 ref 中,并在每次组件在 useEffect 中重新渲染时更新它,如下所示:
// some state
const [counter, setCounter] = useState(0);
// store a ref to the function we will debounce
const increment = useRef(null);
// update the ref every time the component rerenders
useEffect(() =>
increment.current = () =>
setCounter(counter + 1);
;
);
// debounce callback, which we can call (i.e. in button.onClick)
const debouncedIncrement = useCallback(
debounce(() =>
if (increment)
increment.current();
, 1500),
[]
);
// cancel active debounces on component unmount
useEffect(() =>
return () =>
debouncedIncrement.cancel();
;
, []);
代码沙箱:https://codesandbox.io/s/debounced-function-ref-pdrfu?file=/src/index.js
我希望这可以为人们节省几个小时的挣扎
【讨论】:
【参考方案5】:我创建了自己的自定义挂钩,称为 useDebouncedEffect
,它将等待执行 useEffect
,直到状态在延迟期间没有更新。
在此示例中,您的效果将在您停止单击按钮 1 秒后登录到控制台。
沙盒示例 https://codesandbox.io/s/react-use-debounced-effect-6jppw
App.jsx
import useState from "react";
import useDebouncedEffect from "./useDebouncedEffect";
const App = () =>
const [value, setValue] = useState(0)
useDebouncedEffect(() => console.log(value), [value], 1000);
return (
<button onClick=() => setValue(value + 1)>value</button>
)
export default App;
useDebouncedEffect.js
import useEffect from "react";
export const useDebouncedEffect = (effect, deps, delay) =>
useEffect(() =>
const handler = setTimeout(() => effect(), delay);
return () => clearTimeout(handler);
// eslint-disable-next-line react-hooks/exhaustive-deps
, [...deps || [], delay]);
除非您想看到警告,否则禁用详尽的依赖项的注释是必需的,因为 lint 总是会抱怨作为依赖项没有效果。将效果添加为依赖项将在每次渲染时触发 useEffect。相反,您可以将检查添加到 useDebouncedEffect
以确保它正在传递所有依赖项。 (见下文)
向useDebouncedEffect
添加详尽的依赖项检查
如果你想让 eslint 检查 useDebouncedEffect
的详尽依赖关系,你可以将它添加到 package.json
的 eslint 配置中
"eslintConfig":
"extends": [
"react-app"
],
"rules":
"react-hooks/exhaustive-deps": ["warn",
"additionalHooks": "useDebouncedEffect"
]
,
https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks#advanced-configuration
【讨论】:
如果您想知道为什么需要useCallback
,我相信这就是原因:javascript 中的函数不具有引用相等性(即() => === () => // false
)。所以每次组件重新渲染时effect
都和以前不一样了。然而,使用 useCallback
你是在告诉 React '请仅在我的 deps
也发生变化时才考虑我的变化!'
@David Functions 绝对具有引用相等性,这就是您首先需要useCallback
的原因。您的示例是结构平等,而不是引用平等。
@KevinBeal,我想我以前没有听说过结构平等这个词,并且快速的互联网搜索(在 Kotlin 中)说引用是 ===
和结构是 ==
。根据这个逻辑,在我看来,函数在 JavaScript 中具有结构相等性
@David 结构上的平等只是意味着内部的值是相同的,具有相同的键、值等。它是价值平等或其他任何你称之为的东西。【参考方案6】:
我相信这个钩子可以通过提供立即触发的选项正常工作。
import useState, useRef, useEffect from 'react';
const useDebounce = <T>(
value: T,
timeout: number,
immediate: boolean = true
): T =>
const [state, setState] = useState<T>(value);
const handler = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
useEffect(() =>
if (handler.current)
clearTimeout(handler.current);
handler.current = undefined;
else if (immediate)
setState(value);
handler.current = setTimeout(() =>
setState(value);
handler.current = undefined;
, timeout);
, [value, timeout, immediate]);
return state;
;
export default useDebounce;
【讨论】:
【参考方案7】:我做了一个简单的钩子来创建油门实例。
它采用了一种稍微不同的方法,每次都传入要调用的函数,而不是尝试包装它并管理突变。许多其他解决方案没有考虑到调用可能发生变化的函数。模式适用于油门或去抖动。
// useThrottle.js
import React, useCallback from 'react';
import throttle from 'lodash/throttle';
export function useThrottle(timeout = 300, opts = )
return useCallback(throttle((fn, ...args) =>
fn(...args);
, timeout, opts), [timeout]);
示例用法:
...
const throttleX = useThrottle(100);
const updateX = useCallback((event) =>
// do something!
, [someMutableValue])
return (
<div onPointerMove=(event) => throttleX(updateX, event)></div>
)
...
【讨论】:
【参考方案8】:还有一个实现。自定义钩子:
function useThrottle (func, delay)
const [timeout, saveTimeout] = useState(null);
const throttledFunc = function ()
if (timeout)
clearTimeout(timeout);
const newTimeout = setTimeout(() =>
func(...arguments);
if (newTimeout === timeout)
saveTimeout(null);
, delay);
saveTimeout(newTimeout);
return throttledFunc;
及用法:
const throttledFunc = useThrottle(someFunc, 200);
希望对某人有所帮助。
【讨论】:
【参考方案9】:react-table
有一个很好的 useAsyncDebounce
函数,位于 https://react-table.tanstack.com/docs/faq#how-can-i-debounce-rapid-table-state-changes
【讨论】:
【参考方案10】:在 useCallback 钩子的帮助下反跳。
import React, useState, useCallback from 'react';
import debounce from 'lodash.debounce';
function App()
const [value, setValue] = useState('');
const [dbValue, saveToDb] = useState(''); // would be an API call normally
// highlight-starts
const debouncedSave = useCallback(
debounce(nextValue => saveToDb(nextValue), 1000),
[], // will be created only once initially
);
// highlight-ends
const handleChange = event =>
const value: nextValue = event.target;
setValue(nextValue);
// Even though handleChange is created on each render and executed
// it references the same debouncedSave that was created initially
debouncedSave(nextValue);
;
return <div></div>;
【讨论】:
【参考方案11】:const useDebounce = (func: any) =>
const debounceFunc = useRef(null);
useEffect(() =>
if (func)
// @ts-ignore
debounceFunc.current = debounce(func, 1000);
, []);
const debFunc = () =>
if (debounceFunc.current)
return debounceFunc.current;
return func;
;
return debFunc();
;
【讨论】:
【参考方案12】:这里有一个简单的钩子来消除你的调用。
要使用下面的代码,你所要做的就是声明它
const debounceRequest = useDebounce(someFn);
然后这样称呼它
debounceRequest();
实现如下所示
import React from "react";
const useDebounce = (callbackFn: () => any, timeout: number = 500) =>
const [sends, setSends] = React.useState(0);
const stabilizedCallbackFn = React.useCallback(callbackFn, [callbackFn]);
const debounceRequest = () =>
setSends(sends + 1);
;
// 1st send, 2nd send, 3rd send, 4th send ...
// when the 2nd send comes, then 1st set timeout is cancelled via clearInterval
// when the 3rd send comes, then 2nd set timeout is cancelled via clearInterval
// process continues till timeout has passed, then stabilizedCallbackFn gets called
// return () => clearInterval(id) is critical operation since _this_ is what cancels
// the previous send.
// *? return () => clearInterval(id) is called for the previous send when a new send
// is sent. Essentially, within the timeout all but the last send gets called.
React.useEffect(() =>
if (sends > 0)
const id = window.setTimeout(() =>
stabilizedCallbackFn();
setSends(0);
, timeout);
return () =>
return window.clearInterval(id);
;
, [stabilizedCallbackFn, sends, timeout]);
return
debounceRequest,
;
;
export default useDebounce;
【讨论】:
【参考方案13】:这是一个实际的油门挂钩。您可以在屏幕或组件中使用您想要限制的所有功能,并且它们将共享相同的限制。或者您可以多次调用useThrottle()
,并为各个功能设置不同的油门。
这样使用:
import useThrottle from '../hooks/useThrottle';
const [navigateToSignIn, navigateToCreateAccount] = useThrottle([
() => navigation.navigate(NavigationRouteNames.SignIn) ,
() => navigation.navigate(NavigationRouteNames.CreateAccount)
])
还有钩子本身:
import useCallback, useState from "react";
// Throttles all callbacks on a component within the same throttle.
// All callbacks passed in will share the same throttle.
const THROTTLE_DURATION = 500;
export default (callbacks: Array<() => any>) =>
const [isWaiting, setIsWaiting] = useState(false);
const throttledCallbacks = callbacks.map((callback) =>
return useCallback(() =>
if (!isWaiting)
callback()
setIsWaiting(true)
setTimeout(() =>
setIsWaiting(false)
, THROTTLE_DURATION);
, [isWaiting]);
)
return throttledCallbacks;
【讨论】:
【参考方案14】:我写了一个简单的useDebounce
钩子,它考虑了清理,就像useEffect
一样。
import useState, useEffect, useRef, useCallback from "react";
export function useDebounceState<T>(initValue: T, delay: number)
const [value, setValue] = useState<T>(initValue);
const timerRef = useRef(null);
// reset timer when delay changes
useEffect(
function ()
if (timerRef.current)
clearTimeout(timerRef.current);
timerRef.current = null;
,
[delay]
);
const debounceSetValue = useCallback(
function (val)
if (timerRef.current)
clearTimeout(timerRef.current);
timerRef.current = null;
timerRef.current = setTimeout(function ()
setValue(val);
, delay);
,
[delay]
);
return [value, debounceSetValue];
interface DebounceOptions
imediate?: boolean;
initArgs?: any[];
const INIT_VALUE = -1;
export function useDebounce(fn, delay: number, options: DebounceOptions = )
const [num, setNum] = useDebounceState(INIT_VALUE, delay);
// save actual arguments when fn called
const callArgRef = useRef(options.initArgs || []);
// save real callback function
const fnRef = useRef(fn);
// wrapped function
const trigger = useCallback(function ()
callArgRef.current = [].slice.call(arguments);
setNum((prev) =>
return prev + 1;
);
, []);
// update real callback
useEffect(function ()
fnRef.current = fn;
);
useEffect(
function ()
if (num === INIT_VALUE && !options.imediate)
// prevent init call
return;
return fnRef.current.apply(null, callArgRef.current);
,
[num, options.imediate]
);
return trigger;
要点在这里:https://gist.github.com/sophister/9cc74bb7f0509bdd6e763edbbd21ba64
这是现场演示:https://codesandbox.io/s/react-hook-debounce-demo-mgr89?file=/src/App.js
用途:
const debounceChange = useDebounce(function (e)
console.log("debounced text change: " + e.target.value);
, 500);
// can't use debounceChange directly, since react using event pooling
function deboucnedCallback(e)
e.persist();
debounceChange(e);
// later the jsx
<input onChange=deboucnedCallback />
【讨论】:
【参考方案15】:这是我的useDebounce
:
export function useDebounce(callback, timeout, deps)
const timeoutId = useRef();
useEffect(() =>
clearTimeout(timeoutId.current);
timeoutId.current = setTimeout(callback, timeout);
return () => clearTimeout(timeoutId.current);
, deps);
你可以这样使用它:
const TIMEOUT = 500; // wait 500 milliseconds;
export function AppContainer(props)
const dataId = props;
const [data, setData] = useState(null);
//
useDebounce(
async () =>
data = await loadDataFromAPI(dataId);
setData(data);
,
TIMEOUT,
[dataId]
);
//
【讨论】:
【参考方案16】:我已经很晚了,但这是一种去抖setState()
的方法
/**
* Like React.setState, but debounces the setter.
*
* @param * initialValue - The initial value for setState().
* @param int delay - The debounce delay, in milliseconds.
*/
export const useDebouncedState = (initialValue, delay) =>
const [val, setVal] = React.useState(initialValue);
const timeout = React.useRef();
const debouncedSetVal = newVal =>
timeout.current && clearTimeout(timeout.current);
timeout.current = setTimeout(() => setVal(newVal), delay);
;
React.useEffect(() => () => clearTimeout(timeout.current), []);
return [val, debouncedSetVal];
;
【讨论】:
【参考方案17】:我在这里使用 lodash 的 debounce 功能:
import debounce from 'lodash/debounce'
// The function that we want to debounce, for example the function that makes the API calls
const getUsers = (event) =>
// ...
// The magic!
const debouncedGetUsers = useCallback(debounce(getUsers, 500), [])
在你的 JSX 中:
<input value=value onChange=debouncedGetUsers />
【讨论】:
【参考方案18】:它可能是一个很小的自定义钩子,像这样:
useDebounce.js
import React, useState, useEffect from 'react';
export default (value, timeout) =>
const [state, setState] = useState(value);
useEffect(() =>
const handler = setTimeout(() => setState(value), timeout);
return () => clearTimeout(handler);
, [value, timeout]);
return state;
使用示例:
import React, useEffect from 'react';
import useDebounce from '/path/to/useDebounce';
const App = (props) =>
const [state, setState] = useState(title: '');
const debouncedTitle = useDebounce(state.title, 1000);
useEffect(() =>
// do whatever you want with state.title/debouncedTitle
, [debouncedTitle]);
return (
// ...
);
// ...
注意:您可能知道,useEffect
总是在初始渲染时运行,因此如果您使用我的回答,您可能会看到组件的渲染运行两次,不用担心,您只需要编写另一个自定义钩子。查看my other answer了解更多信息。
【讨论】:
我不明白如何避免第二次(或第一次)渲染,即使使用链接的钩子。你能举个例子吗?谢谢 @andreapier 我已经添加了指向另一个自定义钩子的链接,以防止在初始渲染时进行渲染,因为您没有看到它,这里是链接:***.com/a/57941438/3367974 是的,我看到了。我的问题是关于如何让两者一起工作。但是,我改用另一种解决方案,因为这种解决方案(在我看来)存在太多问题。 如果您的意思是将useDebounce
与useDidMountEffect
一起使用,则只需在上面的示例中将useEffect
替换为useDidMountEffect
即可。【参考方案19】:
在我的情况下,我还需要传递事件。和这个一起去:
const MyComponent = () =>
const handleScroll = useMemo(() =>
const throttled = throttle(e => console.log(e.target.scrollLeft), 300);
return e =>
e.persist();
return throttled(e);
;
, []);
return <div onScroll=handleScroll>Content</div>;
;
【讨论】:
【参考方案20】:我用这样的东西,效果很好:
let debouncer = debounce(
f => f(),
1000,
leading: true , // debounce one on leading and one on trailing
);
function App()
let [state, setState] = useState();
useEffect(() => debouncer(()=>
// you can use state here for new state value
),[state])
return <div />
【讨论】:
debounce()
来自哪里?【参考方案21】:
经过一段时间后,我确信使用setTimeout/clearTimeout
(并将其移动到单独的自定义挂钩中)自己处理事情比使用函数式助手要容易得多。在我们将其应用到 useCallback
之后,处理稍后会产生额外的挑战,这些挑战可以由于依赖关系更改而重新创建,但我们不想重置延迟运行。
下面的原始答案
您可能(并且可能需要)useRef
在渲染之间存储值。就像suggested for timers
类似的东西
const App = () =>
const [value, setValue] = useState(0)
const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))
useEffect(() => throttled.current(value), [value])
return (
<button onClick=() => setValue(value + 1)>value</button>
)
至于useCallback
:
它也可以作为
const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);
但是如果我们尝试在 value
更改后重新创建回调:
const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);
我们可能会发现它不会延迟执行:一旦value
被更改,回调就会立即重新创建并执行。
所以我看到useCallback
在延迟运行的情况下不会提供显着优势。这取决于你。
[UPD] 最初是
const throttled = useRef(throttle(() => console.log(value), 1000))
useEffect(throttled.current, [value])
但是这样throttled.current
已经绑定到初始value
(of 0) 通过关闭。因此,即使在下一次渲染中,它也从未改变过。
由于闭包特性,所以在将函数推送到 useRef
时要小心。
【讨论】:
也许我错过了useRef
的部分初始值使初始值接近
@mikes 它取决于(对于 lodash 的版本,有 leading
和 trailing
选项来配置 github.com/lodash/lodash/blob/master/throttle.js)
我们可以使用useRef
创建回调并保留它,但我相信最好使用useCallback
甚至在必要时传递所需的变量,这种情况很少发生。我们可以使用setValue
来更改useCallback
中的值,而无需将value
添加到依赖数组中,甚至可以使用setValue(previous => ...)
访问之前的值。如果我们需要直接访问该值而不更改它,我们可以将其作为参数传递,就像您在示例中使用 useRef
一样,例如 useCallback(throttle((value) => ... , 1000), [])
。
那么这个答案的哪一部分是实际答案?有点曲折。
这个答案太混乱了,同意@coler-j【参考方案22】:
如果您在处理程序中使用它,我相当肯定这是这样做的方法。
function useThrottleScroll()
const savedHandler = useRef();
function handleEvent()
useEffect(() =>
savedHandleEvent.current = handleEvent;
, []);
const throttleOnScroll = useRef(throttle((event) => savedHandleEvent.current(event), 100)).current;
function handleEventPersistence(event)
return throttleOnScroll(event);
return
onScroll: handleEventPersistence,
;
【讨论】:
【参考方案23】:我为这个用例写了两个简单的钩子(use-throttled-effect 和use-debounced-effect),也许它对寻找简单解决方案的其他人有用。
import React, useState from 'react';
import useThrottledEffect from 'use-throttled-effect';
export default function Input()
const [count, setCount] = useState(0);
useEffect(()=>
const interval = setInterval(() => setCount(count=>count+1) ,100);
return ()=>clearInterval(interval);
,[])
useThrottledEffect(()=>
console.log(count);
, 1000 ,[count]);
return (
count
);
【讨论】:
完美运行,谢谢? 测试了两个钩子以上是关于如何通过 React Hook 使用节流阀或去抖动?的主要内容,如果未能解决你的问题,请参考以下文章
如何创建类似于 javascript 节流/去抖动功能的 Rails/Ruby 方法