js防抖节流
使用场景
- 防抖:事件触发后延迟n秒在执行,如果在这n秒内再次触发则重新计时。即在一段时间内只允许事件执行一次,常用于表单提交,输入框防抖
- 节流:事件触发后延迟n秒在执行,并且在这n秒内再次触发事件时不允许执行。即减少一段时间内事件触发的频率,常用于监听滚动条滚动,鼠标移动,窗口大小变化
- 借鉴掘友的神评:防抖类似技能冷却,点击后会有冷却时间;节流类似网络延迟,点击后多少ms后触发
初始延迟执行
// 防抖
function debounce(fn, delay) {
let timer = null;
clearTimeout(timer); // 下次调用时会清除上次的timer, 然后重新延迟
timer = setTimeout(function(){
fn();
}, delay);
}
// 节流
const throttle = (fn, delay) => {
let flag = true
return () => {
if (!flag) return
flag = false // ms 内不允许再次执行
setTimeout(() => {
fn()
flag = true // 重置为true,允许执行
}, delay)
}
}
初始立即执行
// 防抖
function debounce(fn, wait) {
let timer = null
return function () {
let args = arguments
if(timer == null){
fn.apply(this, args)
}
timer && clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
// 节流
function throttle(fun,time){
let t1=0 //初始时间
return function(){
let t2=new Date() //当前时间
if(t2-t1>time){
fun.apply(this,arguments)
t1=t2
}
}
}
react函数组件使用防抖、节流
在实践过程中,函数组件不能使用js方法,因为定时器timer每次都会重新定义,timer是绑定了自己的id,重新定义后每次都为undefined,定时器就失去效果
通过useRef实现,能再函数组件中使用(可能会有bug)
import React, { useRef } from 'react';
import { Button } from 'antd';
const HomePage = () => {
const timeOutRef = useRef(null)
// 防抖
function debounce(fn, delay) {
clearTimeout(timeOutRef.current); // 下次调用时会清除上次的timer, 然后重新延迟
timeOutRef.current = setTimeout(function(){
fn();
}, delay);
}
// 节流
function throttle(fn, delay) {
if (!timeOutRef.current) {
timeOutRef.current = setTimeout(() => {
timeOutRef.current = null
fn()
}, delay)
}
}
function textHandle(args) {
console.log('测试');
}
const handleClick = () => {
debounce(textHandle, 2000)
}
return (
<div>
<Button onClick={handleClick}>主页面</Button>
</div>
);
};
export default HomePage;
通过自定义hook实现
防抖useDebounce
import { useRef, useCallback } from 'react'
type FnType = (...arg: any[]) => any
interface RefType {
fn: FnType
timer: NodeJS.Timeout | null
}
function useDebounce(this: any, fn: FnType, delay: number, dep: any[] = []) {
// 使用 useRef 的目的是:保留上一次的timer,以至于让 if 语句走通,然后清除上一次的 timer
// 否则,没有清除定时器,达不到防抖效果
const { current } = useRef<RefType>({ fn, timer: null })
current.fn = fn
return useCallback((...args) => {
if (current.timer) {
clearTimeout(current.timer)
}
// 下面这个判断,是初始是否执行
if(current.timer == null) {
current.fn.apply(this, args)
}
current.timer = setTimeout(() => {
current.fn.apply(this, args)
}, delay)
}, dep)
}
export default useDebounce
使用
import React from 'react';
import { Button } from 'antd';
import useDebounce from './useDebounce.ts';
const HomePage = () => {
function textHandle(args) {
console.log(args,'测试');
}
const handleClick = useDebounce(textHandle, 2000);
return (
<div>
<Button onClick={handleClick}>测试</Button>
</div>
);
};
export default HomePage;
节流useThrottle
import { useRef, useCallback } from 'react'
type FnType = (...arg: any[]) => any
interface RefType {
fn: FnType
timer: NodeJS.Timeout | null
}
function useThrottle(this: any, fn: FnType, delay: number, dep: any[] = []) {
const { current } = useRef<RefType>({ fn, timer: null })
current.fn = fn
// console.log('this', this)
return useCallback((...args) => {
if (!current.timer) {
current.timer = setTimeout(() => {
current.timer = null
}, delay)
current.fn.apply(this, args)
}
}, dep)
}
export default useThrottle
使用
import React from 'react';
import { Button } from 'antd';
import useThrottle from './useThrottle.ts';
const HomePage = () => {
function textHandle(args) {
console.log(args,'测试');
}
const handleClick = useThrottle(textHandle, 2000);
return (
<div>
<Button onClick={handleClick}>测试</Button>
</div>
);
};
export default HomePage;
总结
- js方法在react中不适用,并且都可以判断初始是否调用
- react方法中需要使用useRef保存计时器,简单版本初始都不会调用,自定义hook初始都会调用