关于react中防抖节流的实践
该文仅记录我自己在实践中的一些思考,比较啰嗦,不喜勿喷,欢迎讨论。
可以翻到最后“新情况”,直接食用。
普通的防抖和节流
以下是工具函数的封装(基础版),实现方法很多,这里仅展示作者常用的。
/**
* 函数节流
* @param fn 执行函数 需要防抖的函数也就是你处理逻辑的地方
* @param time 时间间隔
* @param params 执行函数需要的参数
* @returns
*/
export const throttle = (fn, time, params = {}) => {
let pre = 0;
return (e) => {
const now = new Date().getTime();
if (now - pre >= time) {
fn({ e, ...params });
pre = now;
}
};
};
/**
* 函数防抖
* @param fn 执行函数 需要防抖的函数也就是你处理逻辑的地方
* @param time 时间间隔
* @param params 执行函数需要的参数
* @returns
*/
export const debounce = (fn, time, params = {}) => {
let timer;
return (e) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn({ e, ...params });
}, time);
};
};
实际项目中的应用(该情况不涉及函数组件的refresh)
节流函数生效
(setState情况)接着当我们遇到组件refresh的时候,比如在handleClick中去修改一个state
由于触发refresh,每次渲染的时候onClick都对应的是一个新的节流函数
(也就是控制台中每次执行handleClick之后会再一次执行节流函数的初始化)
接下来你可能会想到用useCallback来对函数进行缓存,尝试一番,如下图还是不行
再想一想 破案了!!!如下图handleThrottle内的逻辑等同于节流函数内部的逻辑
这样一看清晰很多,原因就在于节流函数内的pre值,每次都是let pre = 0,这时候你又该想到useRef了对吧,
重点就是要保证函数和计时标准(有的是timer,有的是上一次的时间戳)是保持不变的。
考虑到这步,其实就该想到封装成hook工具。
封装防抖节流hook
/**
* 函数节流
* @param fn 执行函数 需要防抖的函数也就是你处理逻辑的地方
* @param time 时间间隔
* @param params 执行函数需要的参数
* @param dep useCallback的依赖项
* @returns
*/
export function useThrottle(fn, delay, dep = []) {
const defaultData: { fn: any; pre: number } = { fn, pre: 0 };
const { current = { fn: null, pre: 0 } } = useRef(defaultData);
useEffect(() => {
current.fn = fn;
}, [fn]);
return useCallback((...args) => {
// 用时间间隔做限制
const now = new Date().getTime();
const timeDiff = now - (current?.pre || 0);
if (timeDiff > delay) {
current.pre = now;
current.fn?.(...args);
}
}, dep);
}
实际使用
!!!新情况 在antd table中添加操作按钮 最好对Button进行封装使用
(如下)在columns中的render调用hooks会有报错,!!!主要意思就是要在合适的位置调用hooks,要满足hooks的规范。
{
title: '操作',
fixed: 'right' as const,
width: 260,
render: (_, record) => {
return (
<div>
<Button
type="link"
className="primary-color"
onClick={_utils.useThrottle(handle, 1200, record)}
>
配置
</Button>
</div>
);
},
},
所以,就考虑把按钮封装成新组件,在每个组件中顶层去调用useThrottle
// 用法
<ThrottleButton
type="link"
className="primary-color"
onClick={() => {
onConnect(record);
}}
>
测试
</ThrottleButton>
// 节流按钮组件
export const ThrottleButton = ({ onClick: fn, ...rest }) => {
const handle = _utils.useThrottle(fn, 2000);
return (
<Button onClick={handle} {...rest}>
{rest?.children}
</Button>
);
};
/**
* 节流hook 函数组件会经常更新state导致函数重新声明 内部的即使标准也会重置 这里使用useref 和 usecallback进行缓存
* @param fn 执行函数
* @param delay 节流周期
* @param dep 依赖的数据项
* @returns
*/
export function useThrottle(fn, delay, dep = []) {
const { current } = useRef({ fn, pre: 0 });
useEffect(() => {
current.fn = fn;
}, [fn]);
return useCallback((...args) => {
// 用时间间隔做限制
const now = new Date().getTime();
const timeDiff = now - (current.pre || 0);
if (timeDiff > delay) {
current.pre = now;
current.fn?.(...args);
}
}, dep);
}