干掉重复请求!Vue+Axios全局防抖节流封装,企业级开箱即用

一、前言:为什么接口必须做防抖与节流处理?

在Vue项目开发中,高频重复请求是非常普遍的前端性能问题,不仅加重服务器压力,还会引发页面卡顿、数据错乱、重复提交等各类线上Bug:

  • 搜索框连续输入时,短时间内触发数十次接口请求,造成接口冗余调用、页面频繁渲染抖动
  • 用户快速连续点击提交按钮,触发重复表单提交,导致重复创建数据、重复下单、重复扣款等严重问题
  • 页面滚动、窗口缩放、分页快速切换等操作,频繁触发列表刷新接口,产生大量无效网络请求

绝大多数开发者会选择在业务组件中手写防抖、节流逻辑,但这种方式代码冗余度高、复用性差、容易遗漏场景,维护成本极高。

最优解决方案是:在Axios全局请求层统一封装接口防抖、节流机制,下沉通用能力,无需修改业务代码,全局自动拦截高频重复请求,从根源上解决接口滥用问题。

二、核心概念区分:接口防抖 VS 接口节流

1. 接口防抖(debounce)

核心逻辑 :高频触发请求时,延迟指定时间执行请求;若延迟时间内再次触发,重置计时,最终仅执行最后一次有效请求

适用场景:搜索联想、输入框实时查询、表单字段实时校验、动态筛选等连续输入类业务。

2. 接口节流(throttle)

核心逻辑 :高频触发请求时,严格控制请求频率,固定时间间隔内仅执行一次请求,稀释高频操作带来的无效请求。

适用场景:表单提交、按钮点赞、列表刷新、分页切换、滚动加载等高频点击、刷新类业务。

核心总结 : 所有查询类、输入类接口 统一使用防抖;所有提交类、操作类、刷新类接口统一使用节流。

三、整体封装思路(全局架构设计)

摒弃业务层零散的防抖节流写法,将能力统一封装在Axios请求底层,实现全局统一管控,架构思路清晰、低耦合、易扩展:

  1. 维护全局请求缓存池,统一记录请求计时器、请求状态与类型
  2. 扩展Axios自定义配置项,支持接口单独配置防抖、节流时长,实现精细化管控
  3. 重写Axios核心请求方法,在请求触发前统一执行防抖、节流拦截逻辑
  4. 请求完成、接口报错、路由跳转时自动清空缓存与计时器,杜绝内存泄漏
  5. 支持接口差异化配置,普通接口无侵入、无需改动,完美兼容历史业务

四、完整可落地封装代码

1. 新建 request.js 核心封装文件

基于Vue3+Vite环境封装,同时兼容Vue2,开箱即用、注释详细、可直接上线。

javascript 复制代码
import axios from 'axios'

// 创建axios实例
const service = axios.create({
  baseURL: import.meta.env.VITE_API_URL || '/api',
  timeout: 10000
})

// ====================== 全局防抖节流核心变量 ======================
// 存储请求计时器、请求状态、请求类型,实现请求去重管控
const requestMap = new Map()

// 生成唯一请求key(method + url + 参数序列化)
// 精准区分不同接口、不同参数请求,避免误拦截
function generateReqKey(config) {
  const { method, url, params, data } = config
  return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
}

// ====================== 接口防抖核心逻辑 ======================
function debounceRequest(key, delay, config, resolve) {
  // 存在未执行的计时器,直接清除,重置请求计时
  if (requestMap.has(key)) {
    clearTimeout(requestMap.get(key).timer)
  }

  // 延迟发起请求,仅保留最后一次请求
  const timer = setTimeout(() => {
    requestMap.delete(key)
    resolve(service.originRequest(config))
  }, delay)

  requestMap.set(key, { timer, type: 'debounce' })
}

// ====================== 接口节流核心逻辑 ======================
function throttleRequest(key, delay, config, resolve) {
  const now = Date.now()
  // 固定时间内重复请求直接拦截,稀释请求频率
  if (requestMap.has(key)) {
    const lastTime = requestMap.get(key).time
    if (now - lastTime < delay) {
      return
    }
  }
  // 更新最新请求时间,发起有效请求
  requestMap.set(key, { time: now, type: 'throttle' })
  resolve(service.originRequest(config))
}

// 保存原始请求方法,避免原生能力丢失
service.originRequest = service.request

// 重写request方法,全局接管所有Axios请求
service.request = function(config) {
  return new Promise((resolve) => {
    // 自定义配置:debounce防抖时长、throttle节流时长,默认0不生效
    const { debounce = 0, throttle = 0 } = config
    const reqKey = generateReqKey(config)

    // 防抖优先级更高,避免配置冲突
    if (debounce > 0) {
      debounceRequest(reqKey, debounce, config, resolve)
      return
    }

    if (throttle > 0) {
      throttleRequest(reqKey, throttle, config, resolve)
      return
    }

    // 无配置则执行原生请求,零侵入兼容所有业务
    resolve(service.originRequest(config))
  })
}

// ====================== 请求拦截器 ======================
service.interceptors.request.use(
  (config) => {
    // 可在此统一配置Token、请求头、参数加密等通用逻辑
    return config
  },
  (err) => Promise.reject(err)
)

// ====================== 响应拦截器 ======================
service.interceptors.response.use(
  (res) => {
    return res.data
  },
  (err) => {
    // 接口报错、超时、取消请求时,强制清空缓存,防止接口永久锁死
    const key = generateReqKey(err.config)
    if (requestMap.has(key)) {
      requestMap.delete(key)
    }
    return Promise.reject(err)
  }
)

export default service

五、业务接口使用方式(极简零侵入)

所有接口默认走原生请求逻辑,无需任何改动。仅需对高频接口添加一行配置,即可开启防抖/节流,接入成本极低。

1. 搜索类接口:开启300ms防抖

适用于输入框联想、实时搜索场景,避免连续输入触发大量无效请求。

javascript 复制代码
// api/search.js
import request from '@/utils/request'

// 搜索联想接口防抖处理
export function getSearchList(data) {
  return request({
    url: '/search/list',
    method: 'post',
    data,
    debounce: 300 // 300ms防抖,仅执行最后一次搜索请求
  })
}

2. 提交类接口:开启1000ms节流

适用于表单提交、按钮操作场景,彻底杜绝快速连击导致的重复提交问题。

javascript 复制代码
// api/submit.js
import request from '@/utils/request'

// 表单提交接口节流处理
export function submitForm(data) {
  return request({
    url: '/form/submit',
    method: 'post',
    data,
    throttle: 1000 // 1s节流,1秒内仅允许一次有效提交
  })
}

3. 普通接口(无需防抖节流)

查询单条数据、静态接口等低频请求,无需额外配置,保持原生逻辑。

php 复制代码
export function getInfo() {
  return request({
    url: '/user/info',
    method: 'get'
  })
}

六、关键细节与避坑指南(必看)

1. 为什么需要生成唯一请求Key

通过 请求方式+接口地址+请求参数 生成唯一标识,精准区分每一次请求:

  • 完全相同参数的高频请求,正常执行防抖/节流拦截
  • 参数不同的全新请求,不会被误拦截,保证业务正常流转

2. 防抖节流优先级设计

代码中默认防抖优先级高于节流,避免同一接口同时配置两种规则产生逻辑冲突,贴合日常业务开发习惯。

3. 异常请求必须清空缓存

接口超时、网络异常、手动取消请求时,必须清除当前接口的缓存记录,否则会出现接口永久拦截、无法再次请求的致命问题。

4. 防抖节流时长最佳实践

  • 搜索输入防抖:200~300ms,兼顾操作体验与性能优化,无明显延迟
  • 按钮提交节流:1000ms,彻底杜绝重复提交,适配绝大多数提交场景
  • 列表滚动刷新节流:500ms,平衡刷新实时性与网络开销

5. 完全兼容原有业务

未配置 debounce/throttle 的接口,完全执行原生Axios请求逻辑,零侵入、无缝兼容旧项目,可放心迭代接入。

七、进阶优化:全局清空计时器,杜绝内存泄漏

SPA单页应用路由跳转、页面卸载时,残留的计时器会继续执行无效请求,造成资源浪费。通过全局监听统一清理,彻底优化性能。

javascript 复制代码
import request from '@/utils/request'

// 页面彻底卸载时清空所有计时器与请求缓存
window.addEventListener('beforeunload', () => {
  requestMap.clear()
})

// 路由跳转前清空缓存,防止页面残留无效请求
router.beforeEach((to, from, next) => {
  requestMap.clear()
  next()
})

八、封装核心优势总结

  • 全局统一管控:告别组件零散写法,底层统一拦截规则,维护更简单
  • 精细化粒度控制:支持单个接口独立配置,查询、提交、普通接口差异化处理
  • 彻底解决高频请求问题:完美防连击、防输入刷屏、防滚动冗余请求
  • 零业务侵入:历史代码无需改动,新项目按需开启,接入成本极低
  • 无内存泄漏:请求结束、报错、路由跳转全自动清理缓存与计时器

九、适配业务场景汇总

防抖适配场景:搜索联想、表单实时校验、输入框动态查询、列表动态筛选、远程模糊搜索

节流适配场景:表单提交、订单创建、点赞收藏、列表刷新、分页切换、页面滚动加载

相关推荐
Pedantic23 分钟前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘39 分钟前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆1 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师2 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆2 小时前
VSCode自动格式化三要素
前端
爱勇宝3 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen3 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518135 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode5 小时前
Redis 在生产项目的使用
前端·后端