前言
- 最近在react hooks项目开发中用到了防抖和节流,基于对防抖、节流实现原理的理解,结合业界优秀的hook@umijs/hooks进行了实现,在这里做一个简单的总结。
- 防抖和节流都是防止某一段时间内函数频繁触发,但是这两兄弟之间的原理却不一样。
- 简答的说,防抖是某一段时间内只执行一次,而节流是间隔时间执行。
应用场景
防抖(debounce)
- search搜索框联想搜索,用户在不断输入值时,用防抖来节约请求资源。
- window触发浏览器resize的时候,用防抖来让其只触发一次。
节流(throttle)
- 鼠标不断点击触发,mousedown单位时间内只触发一次。
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断。
防抖(debounce)
原生实现
function debounce(fun, delay) {
return function (args) {
let that = this
let _args = args
clearTimeout(fun.id)
fun.id = setTimeout(function () {
fun.call(that, _args)
}, delay)
}
}
react hooks实现
首先,基于react useEffect实现一个useUpdateEffect,在防抖和节流hook的实现中都会用到:
import { useEffect, useRef } from 'react';
const useUpdateEffect: typeof useEffect = (effect, deps) => {
const isMounted = useRef(false);
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
};
export default useUpdateEffect;
接着,基于react useCallback、useUpdateEffect实现函数防抖useDebounceFn:
- 参数:useDebounceFn接受三个参数:触发回调函数fn、依赖项deps、延迟时间wait;
- 返回值:useDebounceFn返回一个run、cancel方法来执行和取消触发回调函数fn;
import { DependencyList, useCallback, useEffect, useRef } from 'react';
import useUpdateEffect from '../useUpdateEffect';
type noop = (...args: any[]) => any;
export interface ReturnValue<T extends any[]> {
run: (...args: T) => void;
cancel: () => void;
}
function useDebounceFn<T extends any[]>(fn: (...args: T) => any, wait: number): ReturnValue<T>;
function useDebounceFn<T extends any[]>(
fn: (...args: T) => any,
deps: DependencyList,
wait: number,
): ReturnValue<T>;
function useDebounceFn<T extends any[]>(
fn: (...args: T) => any,
deps: DependencyList | number,
wait?: number,
): ReturnValue<T> {
const _deps: DependencyList = (Array.isArray(deps) ? deps : []) as DependencyList;
const _wait: number = typeof deps === 'number' ? deps : wait || 0;
const timer = useRef<any>();
const fnRef = useRef<noop>(fn);
fnRef.current = fn;
const cancel = useCallback(() => {
if (timer.current) {
clearTimeout(timer.current);
}
}, []);
const run = useCallback(
(...args: any) => {
cancel();
timer.current = setTimeout(() => {
fnRef.current(...args);
}, _wait);
},
[_wait, cancel],
);
useUpdateEffect(() => {
run();
return cancel;
}, [..._deps, run]);
useEffect(() => cancel, []);
return {
run,
cancel,
};
}
export default useDebounceFn;
如何使用useDebounceFn这个防抖hook,举个例子:
效果:快速点击button会频繁调用 run,但只会在所有点击完成 500ms 后执行一次相关函数。
import React, { useState } from 'react';
import { Button } from 'antd';
import { useDebounceFn } from '../useDebounceFn';
export default () => {
const [value, setValue] = useState(0);
const { run } = useDebounceFn(() => {
setValue(value + 1);
}, 500);
return (
<div>
<p
style={{
marginTop: 16,
}}
>
{' '}
Clicked count: {value}{' '}
</p>
<Button onClick={run}>Click fast!</Button>
</div>
);
};
节流(throttle)
原生实现
function throttle(fun, delay) {
let last, deferTimer
return function (args) {
let that = this
let _args = arguments
let now = +new Date()
if (last && now < last + delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fun.apply(that, _args)
}, delay)
}else {
last = now
fun.apply(that,_args)
}
}
}
react hooks实现
import { DependencyList, useCallback, useEffect, useRef } from 'react';
import useUpdateEffect from '../useUpdateEffect';
type noop = (...args: any[]) => any;
export interface ReturnValue<T extends any[]> {
run: (...args: T) => void;
cancel: () => void;
}
function useThrottleFn<T extends any[]>(fn: (...args: T) => any, wait: number): ReturnValue<T>;
function useThrottleFn<T extends any[]>(
fn: (...args: T) => any,
deps: DependencyList,
wait: number,
): ReturnValue<T>;
function useThrottleFn<T extends any[]>(
fn: (...args: T) => any,
deps: DependencyList | number,
wait?: number,
): ReturnValue<T> {
const _deps: DependencyList = (Array.isArray(deps) ? deps : []) as DependencyList;
const _wait: number = typeof deps === 'number' ? deps : wait || 0;
const timer = useRef<any>();
const fnRef = useRef<noop>(fn);
fnRef.current = fn;
const currentArgs = useRef<any>([]);
const cancel = useCallback(() => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = undefined;
}, []);
const run = useCallback(
(...args: any) => {
currentArgs.current = args;
if (!timer.current) {
timer.current = setTimeout(() => {
fnRef.current(...currentArgs.current);
timer.current = undefined;
}, _wait);
}
},
[_wait, cancel],
);
useUpdateEffect(() => {
run();
}, [..._deps, run]);
useEffect(() => cancel, []);
return {
run,
cancel,
};
}
export default useThrottleFn;
如何使用useThrottleFn这个节流hook,举个例子:
效果:快速点击button会频繁调用 run,但只会每隔 500ms 执行一次相关函数。
import React, { useState } from 'react';
import { Button } from 'antd';
import { useThrottleFn } from '../useThrottleFn';
export default () => {
const [value, setValue] = useState(0);
const { run } = useThrottleFn(() => {
setValue(value + 1);
}, 500);
return (
<div>
<p
style={{
marginTop: 16,
}}
>
{' '}
Clicked count: {value}{' '}
</p>
<Button onClick={run}>Click fast!</Button>
</div>
);
};
总结
- 函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。
- 函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹。