useDebounce、useDebouncedCallback 和 useThrottledCallback
无痛实现防抖的 React 库!
- 体积小巧,小于 1 KB
- 与 underscore / lodash 实现兼容 — 一次学习,随处使用
- 支持服务器端渲染!
特性
安装
yarn add use-debounce
# 或
npm i use-debounce --save
复制粘贴指南:
use-debounce
简单用法:https://codesandbox.io/s/kx75xzyrq7
防抖 HTTP 请求:https://codesandbox.io/s/rr40wnropq
带 leading
参数的防抖 HTTP 请求:https://codesandbox.io/s/cache-example-with-areas-and-leading-param-119r3i
use-debounce 回调
简单用法:https://codesandbox.io/s/x0jvqrwyq
与原生事件监听器结合:https://codesandbox.io/s/32yqlyo815
取消、最大等待时间和记忆化:https://codesandbox.io/s/4wvmp1xlw4
HTTP 请求:https://codesandbox.io/s/use-debounce-callback-http-y1h3m6
更新日志
https://github.com/xnimorz/use-debounce/blob/master/CHANGELOG.md
简单值的防抖
根据 https://twitter.com/dan_abramov/status/1060729512227467264 WebArchive 链接:https://web.archive.org/web/20210828073432/https://twitter.com/dan_abramov/status/1060729512227467264
import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000);
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>实际值:{text}</p>
<p>防抖值:{value}</p>
</div>
);
}
这个钩子使用浅比较来比较前后的值。这意味着设置一个对象 {}
将触发防抖计时器。如果你需要比较对象(https://github.com/xnimorz/use-debounce/issues/27#issuecomment-496828063),你可以使用下面介绍的 useDebouncedCallback
:
防抖回调
除了用于值的 useDebounce
,你还可以对回调进行防抖,这是更常见的防抖理解方式。
带有 Input 的示例(和 React 回调):https://codesandbox.io/s/x0jvqrwyq
import { useDebouncedCallback } from 'use-debounce';
function Input({ defaultValue }) {
const [value, setValue] = useState(defaultValue);
// 防抖回调
const debounced = useDebouncedCallback(
// 函数
(value) => {
setValue(value);
},
// 延迟时间(毫秒)
1000
);
// 你应该使用 `e => debounced(e.target.value)`,因为 React 使用合成事件
return (
<div>
<input
defaultValue={defaultValue}
onChange={(e) => debounced(e.target.value)}
/>
<p>防抖值:{value}</p>
</div>
);
}
带有滚动的示例(和原生事件监听器):https://codesandbox.io/s/32yqlyo815
function ScrolledComponent() {
// 仅用于显示没有不必要更新的计数器
const updatedCount = useRef(0);
updatedCount.current++;
const [position, setPosition] = useState(window.pageYOffset);
// 防抖回调
const debounced = useDebouncedCallback(
// 函数
() => {
setPosition(window.pageYOffset);
},
// 延迟时间(毫秒)
800
);
useEffect(() => {
const unsubscribe = subscribe(window, 'scroll', debounced);
return () => {
unsubscribe();
};
}, []);
return (
<div style={{ height: 10000 }}>
<div style={{ position: 'fixed', top: 0, left: 0 }}>
<p>防抖后的顶部位置:{position}</p>
<p>组件重新渲染次数:{updatedCount.current}</p>
</div>
</div>
);
}
debounced()
的返回值
对防抖函数 debounced
的后续调用会返回最后一次函数调用的结果。
注意,如果之前没有调用,这意味着你将得到 undefined。你应该在代码中适当地检查这一点。
示例:
it('对防抖函数 `debounced` 的后续调用会返回最后一次函数调用的结果。', () => {
const callback = jest.fn(() => 42);
let callbackCache;
function Component() {
const debounced = useDebouncedCallback(callback, 1000);
callbackCache = debounced;
return null;
}
Enzyme.mount(<Component />);
const result = callbackCache();
expect(callback.mock.calls.length).toBe(0);
expect(result).toBeUndefined();
act(() => {
jest.runAllTimers();
});
expect(callback.mock.calls.length).toBe(1);
const subsequentResult = callbackCache();
expect(callback.mock.calls.length).toBe(1);
expect(subsequentResult).toBe(42);
});
高级用法
取消、最大等待时间和记忆化
useDebounce
和useDebouncedCallback
都支持maxWait
选项。这个参数描述了函数在被调用之前允许延迟的最长时间。- 你可以通过调用
cancel
回调来取消防抖周期
完整示例可以在这里查看 https://codesandbox.io/s/4wvmp1xlw4
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { useDebouncedCallback } from 'use-debounce';
function Input({ defaultValue }) {
const [value, setValue] = useState(defaultValue);
const debounced = useDebouncedCallback(
(value) => {
setValue(value);
},
500,
// 函数被调用之前允许延迟的最长时间:
{ maxWait: 2000 }
);
// 你应该使用 `e => debounced(e.target.value)`,因为 React 使用合成事件
return (
<div>
<input
defaultValue={defaultValue}
onChange={(e) => debounced(e.target.value)}
/>
<p>防抖值:{value}</p>
<button onClick={debounced.cancel}>取消防抖周期</button>
</div>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<Input defaultValue="Hello world" />, rootElement);
useDebounce
调用也有相同的 API:
const [value, {cancel, isPending, flush}] = useDebounce(valueToDebounce);
...
cancel() // 取消待处理的防抖请求
isPending() // 返回是否有待处理的防抖请求
flush() // 立即执行待处理的请求
flush 方法
useDebouncedCallback
有一个 flush
方法。它允许在回调尚未触发时手动调用回调。当用户执行会导致组件卸载的操作,但你需要执行回调时,这个方法很有用。
import React, { useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
function InputWhichFetchesSomeData({ defaultValue, asyncFetchData }) {
const debounced = useDebouncedCallback(
(value) => {
asyncFetchData;
},
500,
{ maxWait: 2000 }
);
// 当组件即将卸载时,如果输入有变化,我们将获取数据。
useEffect(
() => () => {
debounced.flush();
},
[debounced]
);
return (
<input
defaultValue={defaultValue}
onChange={(e) => debounced(e.target.value)}
/>
);
}
isPending 方法
isPending
方法显示组件是否有待处理的回调。适用于 useDebounce
和 useDebouncedCallback
:
import React, { useCallback } from 'react';
function Component({ text }) {
const debounced = useDebouncedCallback(
useCallback(() => {}, []),
500
);
expect(debounced.isPending()).toBeFalsy();
debounced();
expect(debounced.isPending()).toBeTruthy();
debounced.flush();
expect(debounced.isPending()).toBeFalsy();
return <span>{text}</span>;
}
前缘/后缘调用
useDebounce
和 useDebouncedCallback
都支持 leading
和 trailing
选项。leading
参数将在首次调用时立即执行函数。后续调用将被延迟直到超时。trailing
选项控制是否在超时后再次调用回调。
有关前缘防抖调用如何工作的更多信息,请参见:https://lodash.com/docs/#debounce
import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000, { leading: true });
// 当文本第一次改变时,value 会立即更新,
// 但所有后续更改都会被延迟。
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>实际值:{text}</p>
<p>延迟值:{value}</p>
</div>
);
}
选项:
你可以为 useDebounce
和 useDebouncedCallback
提供第三个参数作为附加选项:
选项 | 默认值 | 描述 | 示例 |
---|---|---|---|
maxWait | - | 描述函数被允许延迟的最长时间,超过该时间将被调用 | https://github.com/xnimorz/use-debounce#cancel-maxwait-and-memoization |
leading | - | 此参数将在首次调用时立即执行函数。后续调用将被延迟直到超时 | https://github.com/xnimorz/use-debounce#leading-calls |
trailing | true | 此参数在超时后执行函数 | https://github.com/xnimorz/use-debounce#leading-calls |
equalityFn | (prev, next) => prev === next | [仅用于 useDebounce] 比较函数,用于判断是否应该开始超时 |
useThrottledCallback
从 5.2.0 版本开始,你也可以使用这个库来实现节流回调。 为此,请使用:
import useThrottledCallback from 'use-debounce/useThrottledCallback';
或
import { useThrottledCallback } from 'use-debounce';
几个例子:
-
避免在滚动时过度更新位置。
const scrollHandler = useThrottledCallback(updatePosition, 100); window.addEventListener('scroll', scrollHandler);
-
当点击事件触发时调用
renewToken
,但不超过每 5 分钟一次。const throttled = useThrottledCallback(renewToken, 300000, { 'trailing': false }) <button onClick={throttled}>点击</button>
useThrottledCallback
的所有参数与 useDebouncedCallback
相同,除了 maxWait
选项。因为节流回调不需要这个选项。
特别感谢:
@tryggvigy — 管理了库的许多新功能,如前缘和后缘参数、节流回调等;
@omgovich — 减小了包的大小。