vue3 重复请求处理

614 阅读3分钟

节流并不能完全避免重复请求的问题,通过loading的方式需要挂钩具体的业务代码这两种方式这里就不讲了

方案一:注册自定义防抖指令避免一定时间内的重复请求

原理:利用防抖函数结合vue提供的自定义指令API directive实现

防抖原理:在一定时间内多次触发、只会执行最后一次。通俗点讲,不管触发多少次事件,都会等到事件触发n秒后才执行、如果在n秒内再次触发,那么就以新的事件的时间为准重新计算

自定义指令文件 custom-shell.ts

let timer: ReturnType<typeof setTimeout>

// 防抖函数
function debounce (binding?: any, time?: number) {
    let fn: any = null
    timer && clearTimeout(timer)
    timer = setTimeout(() => {
        if (isType(binding.value) === 'Function') {
            fn = binding.value
        }
        if (isType(binding.value) === 'Object' && isType(binding.value.fn) === 'Function') {
            fn = binding.value.fn
        }
        fn(binding.value.params)
    }, time);
}

let customShells: shellType[] = [
    {
        name: 'debounce',
        desc: '防抖指令',
        comments: `
            传参方式:
                1.v-debounce:time = 函数 例如: v-debounce:2000 =  "function"
            方法1适用于只修改时间节点使用
                2. v-debounce = "{ fn: “function”, time: 1000, params: {}}"
            方法2适用所有情况: fn 触发函数 time: 触发的时间节点 params: 触发函数需要使用的参数
        `,
        shell: {
            mounted: (el: HTMLElement, binding: any) => {
                let time = 1000

                if (isType(binding.arg * 1) === 'Number') {
                    time = binding.arg * 1
                }

                if (isType(binding.value) === 'Object' && binding.value.time) {
                    time = binding.value.time * 1
                }

                el.addEventListener('click', debounce.bind(null, binding, time))
            },
            beforeUnmount: (el: HTMLElement) => {
                el.removeEventListener('click', debounce)
            }
        }
    },
]

在main.ts文件下注册
// 引入指令文件
import customShells from '@uilts/uilts-custom-shell'
// 创建实例
const app = createApp(App)
// 注册自定义指令
customShells.forEach((item: shellType) => {
    app.directive(item.name, item.shell)
})

方案二:利用axios拦截器、取消重复请求

原理:维护一个请求队列padding、在axios请求拦截中判断本次请求的API是否在请求队列中、若不存在,添加进队列,若存在,更新该请求数据。在响应拦截器中、当API成功返回时,将本次请求移出请求队列。当响应报错时,清空队列(主动取消请求也会抛出ERR_CANCELED错误、需要额外处理)

 // 创建axios实例(import.meta.env 我的配置文件 根据你们项目自行修改)
const httpService = axios.create({
  withCredentials: false, // 允许携带cookie
  baseURL: import.meta.env.config_baseUrl,
  timeout: import.meta.env.config_timeOut,

  //修改请求头信息
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  },
});

 // 请求存储对象
let padding: Map<string, any> = new Map()

 // 清空请求队列
function clearPadding () {
  padding.clear()
}

 // 移除请求队列项
function removePadding (requeryUrl: string) {
  if (padding.has(requeryUrl)) {
    padding.delete(requeryUrl)
  }
}

 // 判断是否需要取消请求
function isRequery (requeryUrl: string, cancelFun?: any) {
  if (padding.has(requeryUrl)) {
    // 请求重复时 取消上一次的请求
    padding.get(requeryUrl).abort('取消重复请求')
  }
  // 更新请求队列
  padding.set(requeryUrl, cancelFun)
}

 // 请求拦截
httpService.interceptors.request.use(
  (req: objectType<any>) => {
     // neverCancel控制是否允许重复请求
    if (!req.neverCancel) {
      // 不允许重复请求
      let abortController = new AbortController()
      // 设置请求标识
      req.signal = abortController.signal
      // 判断是否需要取消请求
      isRequery(req.url, abortController)
    }
    return req
  }
);

// 响应拦截
httpService.interceptors.response.use(
  (res: any) => {
    // 请求响应了 不管是成功还是失败 都要将本次请求移除队列
    removePadding(res.config.url)
    // 返回响应信息
    return res.data
  },
  (error: any) => {
    // padding 数据唯一 当是主动取消请求的报错 不需要移除原请求
    if (error.code !== 'ERR_CANCELED') {
      clearPadding()
    }

    return Promise.reject(error)
   }
);

// 部分代码摘出来了 可根据自身业务拓展

纯代码、有其他想法欢迎交流。