react防抖和节流hooks封装

一、防抖和节流概述

防抖(debounce)和节流(throttle)是前端经常用到的工具函数。

在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。

通常情况下,我们习惯于使用lodash提供的工具函数,那么如何自己封装防抖节流的hooks?

首先了解下概念:

  • 防抖: 维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
  • 节流: 维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,会判断是否有延迟调用函数未执行,有则返回,没有则设定在delay时间后触发函数

1、函数防抖

当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

javascript 复制代码
function debounce(fn, ms) {
  let timer;
  return function(...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn(...args)
      timer = null;
    }, ms);
  }
}

2、函数节流

当持续触发事件时,保证一定时间段内只调用一次事件处理函数。

javascript 复制代码
function throttle(fn, ms) {            
    let timer;        
    return function(...args) {                
        if (timer) return;
        canRun = false;
        timer = setTimeout(() => { 
            fn(...args);
            timer = null;
        }, ms);          
    }        
}        

二、react当中防抖细节

防抖函数必须在只执行一次的位置调用。在类组件中,放在constructor里或者变量函数生成的时候都可以,因为类组件只会初始化一次,后续组件中绑定的函数永远是不变的,因此依据闭包原理保存下来的状态会起作用。

而在函数式组件中,每次render时,内部函数会重新生成并绑定到组件上去。当组件只有一个state会影响render时,我们

  1. 狂点按钮,
  2. 只会触发点击事件,不会重新渲染,
  3. 当前组件绑定的事件函数没有变化,防抖函数是同一个,因此防抖起作用

但是当有其他state影响渲染后

  1. 狂点按钮
  2. 触发事件,不重新渲染
  3. count2发生变化,重新渲染
  4. handleClick重新生成并绑定到组件,
  5. 原有函数失效,防抖失效,原有函数延迟一定后执行
  6. counter1发生变化

流程的对比就是这样了,现在你明白为什么正常的防抖函数不能用在 reack hook 里了么?

那么,怎么实现react hook防抖呢?核心思想就是,保证每次渲染时,绑定到组件上的函数是同一个防抖函数。

防抖hook

javascript 复制代码
import { useCallback, useEffect, useRef } from 'react';
export interface UseRefParams {
  fn: (_args: any) => void;
  timer: ReturnType<typeof setTimeout> | null;
}
// React anti shake function
export const useDebounce = (fn: (_args: any) => void, delay = 2000) => {
  const { current } = useRef<UseRefParams>({ fn, timer: null });
  useEffect(() => {
    current.fn = fn;
  }, [current, fn]);
  return useCallback(
    (args: any) => {
      if (current.timer) {
        clearTimeout(current.timer);
      }
      current.timer = setTimeout(() => {
        current.fn(args);
      }, delay);
    },
    [current, delay]
  );
};

三 、节流Hook

在react当中节流注意细节与防抖一样,这里不做陈述,直接上代码:

javascript 复制代码
import { useCallback, useEffect, useRef } from 'react';
export interface UseRefParams {
  fn: (_args: any) => void;
  timer: ReturnType<typeof setTimeout> | null;
}

// React throttling function
export const useThrottle = (fn: (_args: any) => void, delay = 2000) => {
  const { current } = useRef<UseRefParams>({ fn, timer: null });
  useEffect(
    function () {
      current.fn = fn;
    },
    [current, fn]
  );
  return useCallback(
    function f(args: any) {
      if (!current.timer) {
        current.timer = setTimeout(() => {
          current.timer = null;
        }, delay);
        current.fn(args);
      }
    },
    [current, delay]
  );
};
相关推荐
William_Xu几秒前
JavaScript 并发控制
前端
拾年275几秒前
从零手写 Ajax:用原生 XHR 搭建前后端交互全流程
前端·javascript·ajax
光影少年2 分钟前
懒加载与分包:React.lazy + Suspense
前端·react.js·掘金·金石计划
小林ixn17 分钟前
你以为你懂 + 号?看完这篇 Bun + TS 实战,才发现以前全写错了
前端·javascript·typescript
namexingyun39 分钟前
开源前端生态如何成为 AI UI 生成的“燃料“:shadcn/ui、Tailwind CSS、Storybook 技术价值全解剖
java·前端·人工智能·python·ui·开源·ai编程
Zyed43 分钟前
[STM32]Day15读写FLASH+读取ID
前端·stm32·性能优化
jvxiao2 小时前
你真的懂作用域吗?从编译原理角度深度 JS 的作用域
前端·javascript
Darling噜啦啦2 小时前
二叉树与递归算法实战:从树结构到 LeetCode 爬楼梯,一文吃透前端数据结构与递归思维
前端·javascript·数据结构
星栈2 小时前
Rust + Makepad 应用怎么打包发布:Windows、macOS、Linux 全平台交付
前端·rust
Aolith2 小时前
React 路由守卫:我用一个组件替代了 Vue 的 beforeEach
前端·react.js