如何在 React Hook 中使用 throttle 或 debounce?
How to use throttle or debounce with React Hook?
我正在尝试在功能组件中使用 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
中的方法在每次渲染时都重新声明,所以节流效果不起作用。
有人有简单的解决办法吗?
经过一段时间后,我确信使用 setTimeout/clearTimeout
(并将其移至单独的自定义挂钩)自己处理事情比使用功能性助手更容易。稍后处理会在我们将其应用于 useCallback
之后立即产生额外的挑战,由于依赖项更改可以重新创建,但我们不想重置延迟 运行ning.
原回答如下
你可能(并且可能需要)useRef
to store value between renders. Just like it's 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
(0)。因此,即使在下一次渲染中也从未更改过。
由于闭包特性,因此在将函数推入 useRef
时要小心。
我为此用例编写了两个简单的钩子 (use-throttled-effect and 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}
);
}
如果您在处理程序中使用它,我相当确定这是实现它的方法。
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,
};
}
我用过这样的东西,效果很好:
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 />
}
它可以是一个很小的自定义挂钩,如下所示:
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
总是在初始渲染时 运行,因此如果你使用我的答案,你可能会查看组件的 render 运行s 两次,不用担心,您只需要编写另一个自定义挂钩。查看 了解更多信息。
就我而言,我还需要通过该事件。这样做:
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>;
};
我创建了自己的自定义挂钩 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]);
}
除非您想看到警告,否则需要禁用 exhaustive-deps 的注释,因为 lint 总是会抱怨没有作为依赖项生效。添加效果作为依赖项将在每次渲染时触发 useEffect。相反,您可以将检查添加到 useDebouncedEffect
以确保它已通过所有依赖项。 (见下文)
将详尽的依赖项检查添加到 useDebouncedEffect
如果你想让 eslint 检查 useDebouncedEffect
以获取详尽的依赖项,你可以将它添加到 package.json
中的 eslint 配置中
"eslintConfig": {
"extends": [
"react-app"
],
"rules": {
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "useDebouncedEffect"
}]
}
},
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。您可以用自己的 throttle
或 debounce
代码替换 Lodash,例如:
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>
这里使用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} />
我来晚了,但这里有一种去抖动的方法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];
};
这是我的 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]
);
//
}
我写了一个简单的 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} />
我想使用 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.current)
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>
这是一个实际的油门挂钩。您可以在屏幕或组件中使用您想要节流的所有功能,并且它们将共享相同的节流阀。或者您可以多次调用 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;
}
这里有一个简单的钩子来消除你的电话。
要使用下面的代码,你所要做的就是声明它
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;
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();
};
在 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>;
}
react-table
在 https://react-table.tanstack.com/docs/faq#how-can-i-debounce-rapid-table-state-changes
中有一个不错的 useAsyncDebounce
函数
还有一个实现。自定义挂钩:
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);
希望对某人有所帮助。
我做了一个简单的钩子来创建节流实例。
它采用了一种略有不同的方法,每次都传入要调用的函数,而不是尝试包装它并管理突变。许多其他解决方案都没有考虑调用可能发生变化的功能。模式适用于油门或去抖动。
// 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>
)
...
我相信这个钩子可以正常工作,因为它提供了立即触发的选项。
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;
我在尝试解决陈旧状态的问题时想到了以下模式:
我们可以将去抖动函数存储在 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
我希望这会为某人节省几个小时的苦苦挣扎
您可以使用 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>
)
}
我的解决方案类似于此 (功能 useMemo
),但是我将参数直接传递给 useEffect
中的去抖动函数,而不是将其视为依赖项。它通过分离参数(应该是 re-created)和去抖函数(不应该是 re-created)解决了 re-creating 钩子的问题。
const MyComponent: FC<Props> = ({ handler, title }) => {
const payload = useMemo<Payload>(() => ({ title }), [title])
const debouncedHandler = useMemo(() => debounce(handler, 1000), [handler])
useEffect(() => debouncedHandler(payload), [payload, debouncedHandler])
}
function myThrottle(callback, delay) {
var previousTime = 0;
return function (...args) {
let currentTime = Date.now();
let gap = currentTime - previousTime;
if (gap > 0) {
previousTime = currentTime + delay;
callback.call(this, ...args);
}
return;
};
}
在您的功能组件中使用以下代码。
const memoizedCallback = useMemo(() => myThrottle(callback, 3000), []);
使用memoizedCallback作为回调
我正在尝试在功能组件中使用 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
中的方法在每次渲染时都重新声明,所以节流效果不起作用。
有人有简单的解决办法吗?
经过一段时间后,我确信使用 setTimeout/clearTimeout
(并将其移至单独的自定义挂钩)自己处理事情比使用功能性助手更容易。稍后处理会在我们将其应用于 useCallback
之后立即产生额外的挑战,由于依赖项更改可以重新创建,但我们不想重置延迟 运行ning.
原回答如下
你可能(并且可能需要)useRef
to store value between renders. Just like it's 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
(0)。因此,即使在下一次渲染中也从未更改过。
由于闭包特性,因此在将函数推入 useRef
时要小心。
我为此用例编写了两个简单的钩子 (use-throttled-effect and 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}
);
}
如果您在处理程序中使用它,我相当确定这是实现它的方法。
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,
};
}
我用过这样的东西,效果很好:
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 />
}
它可以是一个很小的自定义挂钩,如下所示:
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
总是在初始渲染时 运行,因此如果你使用我的答案,你可能会查看组件的 render 运行s 两次,不用担心,您只需要编写另一个自定义挂钩。查看
就我而言,我还需要通过该事件。这样做:
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>;
};
我创建了自己的自定义挂钩 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]);
}
除非您想看到警告,否则需要禁用 exhaustive-deps 的注释,因为 lint 总是会抱怨没有作为依赖项生效。添加效果作为依赖项将在每次渲染时触发 useEffect。相反,您可以将检查添加到 useDebouncedEffect
以确保它已通过所有依赖项。 (见下文)
将详尽的依赖项检查添加到 useDebouncedEffect
如果你想让 eslint 检查 useDebouncedEffect
以获取详尽的依赖项,你可以将它添加到 package.json
"eslintConfig": {
"extends": [
"react-app"
],
"rules": {
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "useDebouncedEffect"
}]
}
},
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。您可以用自己的 throttle
或 debounce
代码替换 Lodash,例如:
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>
这里使用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} />
我来晚了,但这里有一种去抖动的方法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];
};
这是我的 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]
);
//
}
我写了一个简单的 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} />
我想使用 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.current)
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>
这是一个实际的油门挂钩。您可以在屏幕或组件中使用您想要节流的所有功能,并且它们将共享相同的节流阀。或者您可以多次调用 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;
}
这里有一个简单的钩子来消除你的电话。
要使用下面的代码,你所要做的就是声明它
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;
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();
};
在 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>;
}
react-table
在 https://react-table.tanstack.com/docs/faq#how-can-i-debounce-rapid-table-state-changes
useAsyncDebounce
函数
还有一个实现。自定义挂钩:
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);
希望对某人有所帮助。
我做了一个简单的钩子来创建节流实例。
它采用了一种略有不同的方法,每次都传入要调用的函数,而不是尝试包装它并管理突变。许多其他解决方案都没有考虑调用可能发生变化的功能。模式适用于油门或去抖动。
// 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>
)
...
我相信这个钩子可以正常工作,因为它提供了立即触发的选项。
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;
我在尝试解决陈旧状态的问题时想到了以下模式:
我们可以将去抖动函数存储在 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
我希望这会为某人节省几个小时的苦苦挣扎
您可以使用 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>
)
}
我的解决方案类似于此 useMemo
),但是我将参数直接传递给 useEffect
中的去抖动函数,而不是将其视为依赖项。它通过分离参数(应该是 re-created)和去抖函数(不应该是 re-created)解决了 re-creating 钩子的问题。
const MyComponent: FC<Props> = ({ handler, title }) => {
const payload = useMemo<Payload>(() => ({ title }), [title])
const debouncedHandler = useMemo(() => debounce(handler, 1000), [handler])
useEffect(() => debouncedHandler(payload), [payload, debouncedHandler])
}
function myThrottle(callback, delay) {
var previousTime = 0;
return function (...args) {
let currentTime = Date.now();
let gap = currentTime - previousTime;
if (gap > 0) {
previousTime = currentTime + delay;
callback.call(this, ...args);
}
return;
};
}
在您的功能组件中使用以下代码。
const memoizedCallback = useMemo(() => myThrottle(callback, 3000), []);
使用memoizedCallback作为回调