Vue3与Axios完美集成指南 从基础配置到高级应用全面提升前端开发效率掌握数据请求处理技巧

引言

在现代前端开发中,数据请求是连接前端与后端的关键环节。Vue3作为当前最受欢迎的前端框架之一,其组合式API为开发者提供了更灵活的代码组织方式。而Axios作为一款强大的HTTP客户端库,以其简洁的API和丰富的功能成为前端开发者的首选。将Vue3与Axios完美集成,不仅可以提高开发效率,还能构建出更加健壮、可维护的前端应用。本文将从基础配置到高级应用,全面介绍Vue3与Axios的集成技巧,帮助开发者掌握数据请求处理的精髓。

环境准备

在开始集成之前,我们需要确保开发环境已经准备就绪。首先,确保你的系统已经安装了Node.js(推荐版本14.x或更高)和npm或yarn包管理器。

创建Vue3项目最简单的方式是使用Vite,它提供了更快的构建速度和开发体验:

# 使用npm创建项目 npm create vite@latest my-vue3-axios-app -- --template vue # 或使用yarn创建项目 yarn create vite my-vue3-axios-app --template vue # 进入项目目录 cd my-vue3-axios-app # 安装依赖 npm install # 或 yarn install 

接下来,我们需要安装Axios:

# 使用npm安装axios npm install axios # 或使用yarn安装axios yarn add axios 

现在,我们的基础环境已经准备就绪,可以开始Vue3与Axios的集成了。

基础配置

在Vue3项目中集成Axios,首先需要创建一个基础的Axios实例,并进行一些全局配置。推荐在项目的src目录下创建一个utilsservices文件夹,用于存放网络请求相关的代码。

src/utils目录下创建一个request.js文件:

// src/utils/request.js import axios from 'axios' // 创建axios实例 const service = axios.create({ baseURL: 'https://api.example.com', // API的基础URL timeout: 15000, // 请求超时时间 headers: { 'Content-Type': 'application/json' } }) export default service 

这个基础配置创建了一个Axios实例,设置了API的基础URL、请求超时时间和默认的请求头。接下来,我们可以在Vue组件中使用这个实例发送请求。

例如,在一个Vue组件中:

<template> <div> <h1>用户列表</h1> <ul v-if="users.length"> <li v-for="user in users" :key="user.id">{{ user.name }}</li> </ul> <p v-else>加载中...</p> </div> </template> <script> import { ref, onMounted } from 'vue' import request from '@/utils/request' export default { setup() { const users = ref([]) const fetchUsers = async () => { try { const response = await request.get('/users') users.value = response.data } catch (error) { console.error('获取用户列表失败:', error) } } onMounted(() => { fetchUsers() }) return { users } } } </script> 

这个例子展示了如何在Vue3的组合式API中使用Axios实例发送GET请求,并将响应数据渲染到页面上。

请求封装

在实际项目中,直接在每个组件中使用Axios实例发送请求并不是最佳实践。更好的方式是封装请求方法,使代码更加模块化和可维护。

我们可以创建一个API服务模块,集中管理所有的API请求。在src/services目录下创建一个api.js文件:

// src/services/api.js import request from '@/utils/request' // 用户相关API export const userApi = { // 获取用户列表 getUsers(params) { return request({ url: '/users', method: 'get', params }) }, // 获取用户详情 getUserDetail(id) { return request({ url: `/users/${id}`, method: 'get' }) }, // 创建用户 createUser(data) { return request({ url: '/users', method: 'post', data }) }, // 更新用户 updateUser(id, data) { return request({ url: `/users/${id}`, method: 'put', data }) }, // 删除用户 deleteUser(id) { return request({ url: `/users/${id}`, method: 'delete' }) } } // 文章相关API export const articleApi = { // 获取文章列表 getArticles(params) { return request({ url: '/articles', method: 'get', params }) }, // 获取文章详情 getArticleDetail(id) { return request({ url: `/articles/${id}`, method: 'get' }) }, // 创建文章 createArticle(data) { return request({ url: '/articles', method: 'post', data }) }, // 更新文章 updateArticle(id, data) { return request({ url: `/articles/${id}`, method: 'put', data }) }, // 删除文章 deleteArticle(id) { return request({ url: `/articles/${id}`, method: 'delete' }) } } 

通过这种方式,我们将API请求按照功能模块进行了封装,使代码结构更加清晰。在组件中,我们可以直接导入并使用这些封装好的API方法:

<template> <div> <h1>用户管理</h1> <button @click="loadUsers">加载用户</button> <ul v-if="users.length"> <li v-for="user in users" :key="user.id"> {{ user.name }} <button @click="deleteUser(user.id)">删除</button> </li> </ul> <p v-else>暂无用户数据</p> </div> </template> <script> import { ref } from 'vue' import { userApi } from '@/services/api' export default { setup() { const users = ref([]) const loadUsers = async () => { try { const response = await userApi.getUsers() users.value = response.data } catch (error) { console.error('获取用户列表失败:', error) } } const deleteUser = async (id) => { try { await userApi.deleteUser(id) // 重新加载用户列表 loadUsers() } catch (error) { console.error('删除用户失败:', error) } } return { users, loadUsers, deleteUser } } } </script> 

这种封装方式使组件代码更加简洁,也便于后期维护和修改API请求。

拦截器使用

Axios提供了拦截器功能,允许我们在请求发送前或响应接收后进行一些统一处理。这在处理认证、错误处理、日志记录等方面非常有用。

src/utils/request.js文件中,我们可以添加请求拦截器和响应拦截器:

// src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' // 假设使用Element Plus作为UI框架 // 创建axios实例 const service = axios.create({ baseURL: 'https://api.example.com', timeout: 15000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截器 service.interceptors.request.use( config => { // 在发送请求之前做些什么 // 从localStorage获取token const token = localStorage.getItem('token') // 如果token存在,则在请求头中添加Authorization字段 if (token) { config.headers['Authorization'] = `Bearer ${token}` } // 显示加载状态 // 这里可以使用UI框架的加载组件,例如Element Plus的ElLoading // loadingInstance = ElLoading.service({ fullscreen: true }) return config }, error => { // 对请求错误做些什么 console.error('请求错误:', error) return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { // 对响应数据做点什么 // 隐藏加载状态 // if (loadingInstance) { // loadingInstance.close() // } // 如果接口返回的数据格式是统一的,可以在这里进行统一处理 // 例如:{ code: 200, data: {}, message: 'success' } const res = response.data // 根据后端返回的状态码进行判断 if (res.code !== 200) { ElMessage({ message: res.message || '系统错误', type: 'error', duration: 5 * 1000 }) // 如果是401未授权,则跳转到登录页 if (res.code === 401) { // 清除token localStorage.removeItem('token') // 跳转到登录页 window.location.href = '/login' } return Promise.reject(new Error(res.message || '系统错误')) } else { return res } }, error => { // 对响应错误做点什么 // 隐藏加载状态 // if (loadingInstance) { // loadingInstance.close() // } console.error('响应错误:', error) // 处理HTTP错误状态码 if (error.response) { switch (error.response.status) { case 400: ElMessage.error('请求参数错误') break case 401: ElMessage.error('未授权,请登录') // 清除token localStorage.removeItem('token') // 跳转到登录页 window.location.href = '/login' break case 403: ElMessage.error('拒绝访问') break case 404: ElMessage.error('请求地址不存在') break case 500: ElMessage.error('服务器内部错误') break default: ElMessage.error(`连接错误${error.response.status}`) } } else { // 请求超时或网络错误 if (error.message.includes('timeout')) { ElMessage.error('请求超时!请检查网络') } else { ElMessage.error('网络错误,请检查您的网络连接') } } return Promise.reject(error) } ) export default service 

通过拦截器,我们可以实现:

  1. 统一的认证处理:在请求拦截器中添加token,在响应拦截器中处理401未授权情况。
  2. 统一的错误处理:在响应拦截器中处理HTTP错误状态码,显示友好的错误提示。
  3. 加载状态管理:在请求开始时显示加载状态,在响应结束时隐藏加载状态。
  4. 响应数据预处理:对后端返回的数据格式进行统一处理,提取有效数据。

错误处理

在实际开发中,良好的错误处理机制是必不可少的。除了在拦截器中进行统一错误处理外,我们还可以创建一个专门的错误处理模块,提供更灵活的错误处理方式。

src/utils目录下创建一个errorHandler.js文件:

// src/utils/errorHandler.js import { ElMessage, ElMessageBox } from 'element-plus' // 错误码映射表 const errorCodeMap = { 400: '请求参数错误', 401: '未授权,请登录', 403: '拒绝访问', 404: '请求地址不存在', 408: '请求超时', 500: '服务器内部错误', 501: '服务未实现', 502: '网关错误', 503: '服务不可用', 504: '网关超时', 505: 'HTTP版本不受支持' } // 默认错误处理函数 export const defaultErrorHandler = (error) => { if (error.response) { // 服务器返回了响应,但状态码不在2xx范围内 const status = error.response.status const message = errorCodeMap[status] || `连接错误${status}` ElMessage.error(message) // 如果是401未授权,则跳转到登录页 if (status === 401) { // 清除token localStorage.removeItem('token') // 跳转到登录页 window.location.href = '/login' } } else if (error.request) { // 请求已经发出,但没有收到响应 if (error.message.includes('timeout')) { ElMessage.error('请求超时!请检查网络') } else { ElMessage.error('网络错误,请检查您的网络连接') } } else { // 在设置请求时发生了错误 ElMessage.error('请求失败:' + error.message) } // 返回处理后的错误 return Promise.reject(error) } // 自定义错误处理函数 export const customErrorHandler = (error, customHandler) => { if (typeof customHandler === 'function') { try { const result = customHandler(error) if (result !== false) { defaultErrorHandler(error) } } catch (e) { defaultErrorHandler(error) } } else { defaultErrorHandler(error) } return Promise.reject(error) } // 确认对话框错误处理 export const confirmErrorHandler = (error, confirmOptions = {}) => { const { title = '提示', message = '操作失败,是否重试?', confirmButtonText = '重试', cancelButtonText = '取消' } = confirmOptions return new Promise((resolve, reject) => { ElMessageBox.confirm(message, title, { confirmButtonText, cancelButtonText, type: 'warning' }).then(() => { resolve() }).catch(() => { defaultErrorHandler(error) reject(error) }) }) } 

然后,我们可以修改src/utils/request.js中的响应拦截器,使用这些错误处理函数:

// src/utils/request.js import axios from 'axios' import { defaultErrorHandler } from '@/utils/errorHandler' // 创建axios实例 const service = axios.create({ baseURL: 'https://api.example.com', timeout: 15000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截器 service.interceptors.request.use( config => { // 在发送请求之前做些什么 // 从localStorage获取token const token = localStorage.getItem('token') // 如果token存在,则在请求头中添加Authorization字段 if (token) { config.headers['Authorization'] = `Bearer ${token}` } return config }, error => { // 对请求错误做些什么 return defaultErrorHandler(error) } ) // 响应拦截器 service.interceptors.response.use( response => { // 对响应数据做点什么 // 如果接口返回的数据格式是统一的,可以在这里进行统一处理 // 例如:{ code: 200, data: {}, message: 'success' } const res = response.data // 根据后端返回的状态码进行判断 if (res.code !== 200) { // 创建一个模拟的响应错误对象 const error = new Error(res.message || '系统错误') error.response = { status: res.code, data: res } return defaultErrorHandler(error) } else { return res } }, error => { // 对响应错误做点什么 return defaultErrorHandler(error) } ) export default service 

在组件中,我们可以根据需要选择不同的错误处理方式:

<template> <div> <h1>用户管理</h1> <button @click="loadUsers">加载用户</button> <button @click="loadUsersWithRetry">加载用户(带重试)</button> <ul v-if="users.length"> <li v-for="user in users" :key="user.id"> {{ user.name }} </li> </ul> <p v-else>暂无用户数据</p> </div> </template> <script> import { ref } from 'vue' import { userApi } from '@/services/api' import { customErrorHandler, confirmErrorHandler } from '@/utils/errorHandler' export default { setup() { const users = ref([]) // 使用默认错误处理 const loadUsers = async () => { try { const response = await userApi.getUsers() users.value = response.data } catch (error) { // 错误已经在拦截器中处理,这里可以不做处理 // 或者根据需要进行额外处理 console.error('加载用户失败:', error) } } // 使用自定义错误处理 const loadUsersWithCustomError = async () => { try { const response = await userApi.getUsers() users.value = response.data } catch (error) { // 使用自定义错误处理 customErrorHandler(error, (err) => { console.error('自定义错误处理:', err) // 返回false表示不使用默认错误处理 // 返回其他值或不返回则使用默认错误处理 return false }) } } // 使用确认对话框错误处理 const loadUsersWithRetry = async () => { try { const response = await userApi.getUsers() users.value = response.data } catch (error) { try { // 显示确认对话框,用户点击重试后重新执行loadUsersWithRetry await confirmErrorHandler(error, { message: '加载用户失败,是否重试?' }) // 用户点击重试,重新执行 loadUsersWithRetry() } catch (err) { // 用户点击取消,不做处理 console.error('用户取消了重试') } } } return { users, loadUsers, loadUsersWithRetry } } } </script> 

通过这种方式,我们可以根据不同的业务场景选择合适的错误处理方式,提高用户体验。

取消请求

在实际应用中,我们经常需要处理重复请求和请求取消的问题。例如,当用户快速点击同一个按钮时,可能会发送多个相同的请求;或者当用户离开当前页面时,应该取消尚未完成的请求,以避免不必要的资源浪费。

Axios提供了取消请求的功能,我们可以利用这个功能来解决上述问题。

首先,在src/utils目录下创建一个cancelToken.js文件,用于管理取消令牌:

// src/utils/cancelToken.js import axios from 'axios' // 存储每个请求的取消函数和标识 const pendingRequests = {} // 生成请求的唯一标识 const generateRequestKey = (config) => { const { method, url, params, data } = config return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&') } // 添加请求到pendingRequests export const addPendingRequest = (config) => { const requestKey = generateRequestKey(config) // 如果请求已经存在,则取消之前的请求 if (pendingRequests[requestKey]) { const cancel = pendingRequests[requestKey] cancel('取消重复请求') } // 创建取消令牌和取消函数 const cancelToken = new axios.CancelToken((cancel) => { pendingRequests[requestKey] = cancel }) // 将取消令牌添加到请求配置中 config.cancelToken = cancelToken } // 从pendingRequests中移除请求 export const removePendingRequest = (config) => { const requestKey = generateRequestKey(config) if (pendingRequests[requestKey]) { delete pendingRequests[requestKey] } } // 取消所有请求 export const cancelAllRequests = (message = '取消所有请求') => { Object.keys(pendingRequests).forEach(requestKey => { const cancel = pendingRequests[requestKey] if (typeof cancel === 'function') { cancel(message) } }) // 清空pendingRequests for (const key in pendingRequests) { delete pendingRequests[key] } } 

然后,修改src/utils/request.js文件,在请求拦截器中添加取消令牌,在响应拦截器中移除已完成的请求:

// src/utils/request.js import axios from 'axios' import { defaultErrorHandler } from '@/utils/errorHandler' import { addPendingRequest, removePendingRequest } from '@/utils/cancelToken' // 创建axios实例 const service = axios.create({ baseURL: 'https://api.example.com', timeout: 15000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截器 service.interceptors.request.use( config => { // 在发送请求之前做些什么 // 从localStorage获取token const token = localStorage.getItem('token') // 如果token存在,则在请求头中添加Authorization字段 if (token) { config.headers['Authorization'] = `Bearer ${token}` } // 添加取消令牌 addPendingRequest(config) return config }, error => { // 对请求错误做些什么 removePendingRequest(error.config || {}) return defaultErrorHandler(error) } ) // 响应拦截器 service.interceptors.response.use( response => { // 对响应数据做点什么 // 从pendingRequests中移除已完成的请求 removePendingRequest(response.config) // 如果接口返回的数据格式是统一的,可以在这里进行统一处理 // 例如:{ code: 200, data: {}, message: 'success' } const res = response.data // 根据后端返回的状态码进行判断 if (res.code !== 200) { // 创建一个模拟的响应错误对象 const error = new Error(res.message || '系统错误') error.response = { status: res.code, data: res } return defaultErrorHandler(error) } else { return res } }, error => { // 对响应错误做点什么 // 从pendingRequests中移除已完成的请求 if (error.config) { removePendingRequest(error.config) } // 如果是取消请求导致的错误,则不显示错误提示 if (axios.isCancel(error)) { console.log('请求被取消:', error.message) return Promise.reject(error) } return defaultErrorHandler(error) } ) export default service 

在Vue3中,我们可以在组件中使用onBeforeUnmount生命周期钩子来取消所有未完成的请求:

<template> <div> <h1>用户列表</h1> <button @click="loadUsers">加载用户</button> <ul v-if="users.length"> <li v-for="user in users" :key="user.id">{{ user.name }}</li> </ul> <p v-else>加载中...</p> </div> </template> <script> import { ref, onBeforeUnmount } from 'vue' import { userApi } from '@/services/api' import { cancelAllRequests } from '@/utils/cancelToken' export default { setup() { const users = ref([]) const loadUsers = async () => { try { const response = await userApi.getUsers() users.value = response.data } catch (error) { // 如果是取消请求导致的错误,则不做处理 if (error.message && error.message.includes('取消')) { return } console.error('获取用户列表失败:', error) } } // 在组件卸载前取消所有请求 onBeforeUnmount(() => { cancelAllRequests('组件卸载,取消所有请求') }) return { users, loadUsers } } } </script> 

通过这种方式,我们可以有效地处理重复请求和请求取消的问题,提高应用的性能和用户体验。

请求优化

在实际项目中,我们还可以通过一些高级技巧来优化请求,例如请求缓存、请求重试、请求节流等。

请求缓存

对于一些不经常变化的数据,我们可以使用缓存来减少不必要的网络请求,提高应用性能。

src/utils目录下创建一个cache.js文件:

// src/utils/cache.js // 简单的内存缓存实现 class MemoryCache { constructor() { this.cache = new Map() this.timers = new Map() } // 设置缓存 set(key, value, ttl = 0) { // 如果已经存在相同的key,则清除之前的定时器 if (this.timers.has(key)) { clearTimeout(this.timers.get(key)) this.timers.delete(key) } // 设置缓存 this.cache.set(key, value) // 如果设置了过期时间,则设置定时器 if (ttl > 0) { const timer = setTimeout(() => { this.delete(key) }, ttl * 1000) this.timers.set(key, timer) } } // 获取缓存 get(key) { return this.cache.get(key) } // 删除缓存 delete(key) { // 清除定时器 if (this.timers.has(key)) { clearTimeout(this.timers.get(key)) this.timers.delete(key) } // 删除缓存 return this.cache.delete(key) } // 清空所有缓存 clear() { // 清除所有定时器 this.timers.forEach(timer => { clearTimeout(timer) }) this.timers.clear() // 清空缓存 this.cache.clear() } // 检查是否存在某个key has(key) { return this.cache.has(key) } } // 创建缓存实例 const cache = new MemoryCache() // 生成缓存key const generateCacheKey = (config) => { const { method, url, params, data } = config return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&') } // 缓存请求 export const cacheRequest = (config, ttl = 60) => { const cacheKey = generateCacheKey(config) // 如果缓存中存在数据,则直接返回 if (cache.has(cacheKey)) { const cachedData = cache.get(cacheKey) return Promise.resolve({ data: cachedData, cached: true }) } // 否则发送请求 return new Promise((resolve, reject) => { // 保存原始的success和error回调 const originalSuccess = config.success const originalError = config.error // 重写success回调 config.success = (response) => { // 将响应数据存入缓存 cache.set(cacheKey, response, ttl) // 调用原始的success回调 if (typeof originalSuccess === 'function') { originalSuccess(response) } // 返回响应数据 resolve({ data: response, cached: false }) } // 重写error回调 config.error = (error) => { // 调用原始的error回调 if (typeof originalError === 'function') { originalError(error) } // 返回错误 reject(error) } // 发送请求 axios(config) }) } // 清除指定请求的缓存 export const clearCache = (config) => { const cacheKey = generateCacheKey(config) cache.delete(cacheKey) } // 清除所有缓存 export const clearAllCache = () => { cache.clear() } 

然后,我们可以修改src/services/api.js文件,为一些适合缓存的API添加缓存功能:

// src/services/api.js import request from '@/utils/request' import { cacheRequest, clearCache } from '@/utils/cache' // 用户相关API export const userApi = { // 获取用户列表(带缓存,缓存时间60秒) getUsers(params) { return cacheRequest({ url: '/users', method: 'get', params }, 60) }, // 获取用户详情(带缓存,缓存时间120秒) getUserDetail(id) { return cacheRequest({ url: `/users/${id}`, method: 'get' }, 120) }, // 创建用户(创建成功后清除用户列表缓存) createUser(data) { return request({ url: '/users', method: 'post', data }).then(response => { // 清除用户列表缓存 clearCache({ url: '/users', method: 'get' }) return response }) }, // 更新用户(更新成功后清除用户列表和用户详情缓存) updateUser(id, data) { return request({ url: `/users/${id}`, method: 'put', data }).then(response => { // 清除用户列表缓存 clearCache({ url: '/users', method: 'get' }) // 清除用户详情缓存 clearCache({ url: `/users/${id}`, method: 'get' }) return response }) }, // 删除用户(删除成功后清除用户列表和用户详情缓存) deleteUser(id) { return request({ url: `/users/${id}`, method: 'delete' }).then(response => { // 清除用户列表缓存 clearCache({ url: '/users', method: 'get' }) // 清除用户详情缓存 clearCache({ url: `/users/${id}`, method: 'get' }) return response }) } } 

在组件中使用带缓存的API:

<template> <div> <h1>用户列表</h1> <button @click="loadUsers">加载用户</button> <p v-if="isCached">数据来自缓存</p> <ul v-if="users.length"> <li v-for="user in users" :key="user.id">{{ user.name }}</li> </ul> <p v-else>加载中...</p> </div> </template> <script> import { ref } from 'vue' import { userApi } from '@/services/api' export default { setup() { const users = ref([]) const isCached = ref(false) const loadUsers = async () => { try { const response = await userApi.getUsers() users.value = response.data isCached.value = response.cached } catch (error) { console.error('获取用户列表失败:', error) } } return { users, isCached, loadUsers } } } </script> 

请求重试

对于一些临时性的网络错误,我们可以通过请求重试来提高请求的成功率。

src/utils目录下创建一个retry.js文件:

// src/utils/retry.js // 请求重试函数 export const retryRequest = (requestFn, maxRetry = 3, delay = 1000) => { return new Promise((resolve, reject) => { let retryCount = 0 const attempt = () => { retryCount++ requestFn() .then(resolve) .catch(error => { // 如果是取消请求导致的错误,则不重试 if (error.message && error.message.includes('取消')) { return reject(error) } // 如果重试次数达到上限,则拒绝 if (retryCount >= maxRetry) { return reject(error) } // 延迟后重试 console.log(`请求失败,${delay / 1000}秒后进行第${retryCount + 1}次重试...`) setTimeout(attempt, delay) }) } attempt() }) } 

然后,我们可以修改src/services/api.js文件,为一些重要的API添加重试功能:

// src/services/api.js import request from '@/utils/request' import { retryRequest } from '@/utils/retry' // 用户相关API export const userApi = { // 获取用户列表(带重试,最多重试3次,每次间隔1秒) getUsers(params) { return retryRequest(() => { return request({ url: '/users', method: 'get', params }) }, 3, 1000) }, // 获取用户详情 getUserDetail(id) { return request({ url: `/users/${id}`, method: 'get' }) }, // 创建用户 createUser(data) { return request({ url: '/users', method: 'post', data }) }, // 更新用户 updateUser(id, data) { return request({ url: `/users/${id}`, method: 'put', data }) }, // 删除用户 deleteUser(id) { return request({ url: `/users/${id}`, method: 'delete' }) } } 

请求节流

对于一些频繁触发的请求,例如搜索框输入时的实时搜索,我们可以使用节流来控制请求的频率,避免发送过多的请求。

src/utils目录下创建一个throttle.js文件:

// src/utils/throttle.js // 节流函数 export const throttle = (fn, delay = 300) => { let timer = null let lastTime = 0 return function(...args) { const now = Date.now() // 如果距离上次执行的时间小于延迟时间,则设置定时器 if (now - lastTime < delay) { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { lastTime = now fn.apply(this, args) timer = null }, delay - (now - lastTime)) } else { // 否则立即执行 lastTime = now fn.apply(this, args) } } } // 节流请求函数 export const throttleRequest = (requestFn, delay = 300) => { const throttledFn = throttle(requestFn, delay) return function(...args) { return throttledFn.apply(this, args) } } 

在组件中使用节流请求:

<template> <div> <h1>用户搜索</h1> <input v-model="searchKeyword" @input="handleSearch" placeholder="输入用户名搜索" /> <ul v-if="searchResults.length"> <li v-for="user in searchResults" :key="user.id">{{ user.name }}</li> </ul> <p v-else-if="searchKeyword && !searching">没有找到相关用户</p> <p v-else-if="searching">搜索中...</p> </div> </template> <script> import { ref } from 'vue' import { userApi } from '@/services/api' import { throttleRequest } from '@/utils/throttle' export default { setup() { const searchKeyword = ref('') const searchResults = ref([]) const searching = ref(false) // 创建节流搜索函数,延迟300毫秒 const throttledSearch = throttleRequest(async (keyword) => { if (!keyword.trim()) { searchResults.value = [] return } searching.value = true try { const response = await userApi.getUsers({ keyword }) searchResults.value = response.data } catch (error) { console.error('搜索用户失败:', error) searchResults.value = [] } finally { searching.value = false } }, 300) const handleSearch = () => { throttledSearch(searchKeyword.value) } return { searchKeyword, searchResults, searching, handleSearch } } } </script> 

通过这些优化技巧,我们可以显著提高应用的性能和用户体验。

TypeScript支持

对于使用TypeScript的项目,我们可以为Axios添加类型支持,提高代码的可维护性和开发体验。

首先,我们需要安装类型定义文件:

# 使用npm安装 npm install @types/axios --save-dev # 或使用yarn安装 yarn add @types/axios --dev 

然后,我们可以创建类型定义文件,为API请求和响应添加类型。

src/types目录下创建api.d.ts文件:

// src/types/api.d.ts // 用户类型 export interface User { id: number name: string email: string avatar?: string createdAt: string updatedAt: string } // 文章类型 export interface Article { id: number title: string content: string authorId: number author?: User createdAt: string updatedAt: string } // 分页参数 export interface PaginationParams { page: number pageSize: number } // 分页响应 export interface PaginatedResponse<T> { data: T[] pagination: { page: number pageSize: number total: number totalPages: number } } // API响应 export interface ApiResponse<T = any> { code: number data: T message: string } 

接下来,修改src/utils/request.ts文件(注意文件扩展名改为.ts):

// src/utils/request.ts import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import { defaultErrorHandler } from '@/utils/errorHandler' import { addPendingRequest, removePendingRequest } from '@/utils/cancelToken' import { ApiResponse } from '@/types/api' // 扩展AxiosRequestConfig接口,添加自定义配置 declare module 'axios' { interface AxiosRequestConfig { // 是否显示错误提示 showError?: boolean // 是否显示加载状态 loading?: boolean // 是否需要token needToken?: boolean // 是否缓存请求 cache?: boolean // 缓存时间(秒) cacheTTL?: number } } // 创建axios实例 const service: AxiosInstance = axios.create({ baseURL: 'https://api.example.com', timeout: 15000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截器 service.interceptors.request.use( (config: AxiosRequestConfig) => { // 在发送请求之前做些什么 // 如果需要token,则添加到请求头中 if (config.needToken !== false) { const token = localStorage.getItem('token') if (token) { config.headers = config.headers || {} config.headers['Authorization'] = `Bearer ${token}` } } // 添加取消令牌 addPendingRequest(config) return config }, (error) => { // 对请求错误做些什么 removePendingRequest(error.config || {}) return defaultErrorHandler(error) } ) // 响应拦截器 service.interceptors.response.use( (response: AxiosResponse<ApiResponse>) => { // 对响应数据做点什么 // 从pendingRequests中移除已完成的请求 removePendingRequest(response.config) // 如果接口返回的数据格式是统一的,可以在这里进行统一处理 const res = response.data // 根据后端返回的状态码进行判断 if (res.code !== 200) { // 创建一个模拟的响应错误对象 const error = new Error(res.message || '系统错误') error.response = { status: res.code, data: res } // 如果配置了不显示错误提示,则直接返回错误 if (response.config?.showError !== false) { return defaultErrorHandler(error) } return Promise.reject(error) } else { return res } }, (error) => { // 对响应错误做点什么 // 从pendingRequests中移除已完成的请求 if (error.config) { removePendingRequest(error.config) } // 如果是取消请求导致的错误,则不显示错误提示 if (axios.isCancel(error)) { console.log('请求被取消:', error.message) return Promise.reject(error) } // 如果配置了不显示错误提示,则直接返回错误 if (error.config?.showError !== false) { return defaultErrorHandler(error) } return Promise.reject(error) } ) export default service 

然后,修改src/services/api.ts文件(注意文件扩展名改为.ts):

// src/services/api.ts import request, { AxiosRequestConfig } from '@/utils/request' import { User, Article, PaginationParams, PaginatedResponse, ApiResponse } from '@/types/api' // 用户相关API export const userApi = { // 获取用户列表 getUsers(params?: PaginationParams & { keyword?: string }): Promise<ApiResponse<PaginatedResponse<User>>> { return request({ url: '/users', method: 'get', params }) }, // 获取用户详情 getUserDetail(id: number): Promise<ApiResponse<User>> { return request({ url: `/users/${id}`, method: 'get' }) }, // 创建用户 createUser(data: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<ApiResponse<User>> { return request({ url: '/users', method: 'post', data }) }, // 更新用户 updateUser(id: number, data: Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>): Promise<ApiResponse<User>> { return request({ url: `/users/${id}`, method: 'put', data }) }, // 删除用户 deleteUser(id: number): Promise<ApiResponse<void>> { return request({ url: `/users/${id}`, method: 'delete' }) } } // 文章相关API export const articleApi = { // 获取文章列表 getArticles(params?: PaginationParams & { keyword?: string; authorId?: number }): Promise<ApiResponse<PaginatedResponse<Article>>> { return request({ url: '/articles', method: 'get', params }) }, // 获取文章详情 getArticleDetail(id: number): Promise<ApiResponse<Article>> { return request({ url: `/articles/${id}`, method: 'get' }) }, // 创建文章 createArticle(data: Omit<Article, 'id' | 'createdAt' | 'updatedAt' | 'author'>): Promise<ApiResponse<Article>> { return request({ url: '/articles', method: 'post', data }) }, // 更新文章 updateArticle(id: number, data: Partial<Omit<Article, 'id' | 'createdAt' | 'updatedAt' | 'author'>>): Promise<ApiResponse<Article>> { return request({ url: `/articles/${id}`, method: 'put', data }) }, // 删除文章 deleteArticle(id: number): Promise<ApiResponse<void>> { return request({ url: `/articles/${id}`, method: 'delete' }) } } 

最后,在Vue组件中使用TypeScript:

<template> <div> <h1>用户列表</h1> <button @click="loadUsers">加载用户</button> <ul v-if="users.length"> <li v-for="user in users" :key="user.id"> {{ user.name }} ({{ user.email }}) <button @click="deleteUser(user.id)">删除</button> </li> </ul> <p v-else>加载中...</p> </div> </template> <script lang="ts"> import { defineComponent, ref, onMounted } from 'vue' import { User } from '@/types/api' import { userApi } from '@/services/api' export default defineComponent({ setup() { const users = ref<User[]>([]) const loadUsers = async () => { try { const response = await userApi.getUsers({ page: 1, pageSize: 10 }) users.value = response.data.data } catch (error) { console.error('获取用户列表失败:', error) } } const deleteUser = async (id: number) => { try { await userApi.deleteUser(id) // 重新加载用户列表 loadUsers() } catch (error) { console.error('删除用户失败:', error) } } onMounted(() => { loadUsers() }) return { users, loadUsers, deleteUser } } }) </script> 

通过使用TypeScript,我们可以为API请求和响应添加类型检查,减少运行时错误,提高代码的可维护性和开发体验。

最佳实践

在实际项目中,我们可以结合前面介绍的各种技巧,构建一个高效、可维护的数据请求层。下面是一个完整的最佳实践示例。

项目结构

首先,我们来看一下一个典型的项目结构:

src/ ├── api/ # API请求相关 │ ├── modules/ # API模块 │ │ ├── user.ts # 用户相关API │ │ ├── article.ts # 文章相关API │ │ └── ... # 其他模块API │ ├── index.ts # API入口文件 │ └── types.ts # API类型定义 ├── assets/ # 静态资源 ├── components/ # 组件 ├── composables/ # 组合式函数 │ ├── useRequest.ts # 请求组合式函数 │ └── ... # 其他组合式函数 ├── hooks/ # 钩子函数 ├── layouts/ # 布局组件 ├── router/ # 路由配置 ├── store/ # 状态管理 ├── styles/ # 样式文件 ├── types/ # 类型定义 │ ├── api.d.ts # API类型定义 │ └── ... # 其他类型定义 ├── utils/ # 工具函数 │ ├── request.ts # Axios请求实例 │ ├── errorHandler.ts # 错误处理 │ ├── cancelToken.ts # 取消令牌 │ ├── cache.ts # 缓存 │ ├── retry.ts # 重试 │ └── throttle.ts # 节流 ├── App.vue # 根组件 ├── main.ts # 入口文件 └── shims-vue.d.ts # Vue类型声明 

API模块化

我们将API按照功能模块进行划分,每个模块负责处理相关的API请求。

src/api/types.ts:

// src/api/types.ts // 用户类型 export interface User { id: number name: string email: string avatar?: string createdAt: string updatedAt: string } // 文章类型 export interface Article { id: number title: string content: string authorId: number author?: User createdAt: string updatedAt: string } // 分页参数 export interface PaginationParams { page: number pageSize: number } // 分页响应 export interface PaginatedResponse<T> { data: T[] pagination: { page: number pageSize: number total: number totalPages: number } } // API响应 export interface ApiResponse<T = any> { code: number data: T message: string } // 请求配置 export interface RequestConfig { // 是否显示错误提示 showError?: boolean // 是否显示加载状态 loading?: boolean // 是否需要token needToken?: boolean // 是否缓存请求 cache?: boolean // 缓存时间(秒) cacheTTL?: number // 重试次数 retry?: number // 重试延迟(毫秒) retryDelay?: number } 

src/api/modules/user.ts:

// src/api/modules/user.ts import { request } from '@/utils/request' import { User, PaginationParams, PaginatedResponse, ApiResponse, RequestConfig } from '@/api/types' export interface UserApi { // 获取用户列表 getUsers: (params?: PaginationParams & { keyword?: string }, config?: RequestConfig) => Promise<ApiResponse<PaginatedResponse<User>>> // 获取用户详情 getUserDetail: (id: number, config?: RequestConfig) => Promise<ApiResponse<User>> // 创建用户 createUser: (data: Omit<User, 'id' | 'createdAt' | 'updatedAt'>, config?: RequestConfig) => Promise<ApiResponse<User>> // 更新用户 updateUser: (id: number, data: Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>, config?: RequestConfig) => Promise<ApiResponse<User>> // 删除用户 deleteUser: (id: number, config?: RequestConfig) => Promise<ApiResponse<void>> } const userApi: UserApi = { // 获取用户列表 getUsers(params, config = {}) { return request({ url: '/users', method: 'get', params, ...config }) }, // 获取用户详情 getUserDetail(id, config = {}) { return request({ url: `/users/${id}`, method: 'get', ...config }) }, // 创建用户 createUser(data, config = {}) { return request({ url: '/users', method: 'post', data, ...config }) }, // 更新用户 updateUser(id, data, config = {}) { return request({ url: `/users/${id}`, method: 'put', data, ...config }) }, // 删除用户 deleteUser(id, config = {}) { return request({ url: `/users/${id}`, method: 'delete', ...config }) } } export default userApi 

src/api/modules/article.ts:

// src/api/modules/article.ts import { request } from '@/utils/request' import { Article, PaginationParams, PaginatedResponse, ApiResponse, RequestConfig } from '@/api/types' export interface ArticleApi { // 获取文章列表 getArticles: (params?: PaginationParams & { keyword?: string; authorId?: number }, config?: RequestConfig) => Promise<ApiResponse<PaginatedResponse<Article>>> // 获取文章详情 getArticleDetail: (id: number, config?: RequestConfig) => Promise<ApiResponse<Article>> // 创建文章 createArticle: (data: Omit<Article, 'id' | 'createdAt' | 'updatedAt' | 'author'>, config?: RequestConfig) => Promise<ApiResponse<Article>> // 更新文章 updateArticle: (id: number, data: Partial<Omit<Article, 'id' | 'createdAt' | 'updatedAt' | 'author'>>, config?: RequestConfig) => Promise<ApiResponse<Article>> // 删除文章 deleteArticle: (id: number, config?: RequestConfig) => Promise<ApiResponse<void>> } const articleApi: ArticleApi = { // 获取文章列表 getArticles(params, config = {}) { return request({ url: '/articles', method: 'get', params, ...config }) }, // 获取文章详情 getArticleDetail(id, config = {}) { return request({ url: `/articles/${id}`, method: 'get', ...config }) }, // 创建文章 createArticle(data, config = {}) { return request({ url: '/articles', method: 'post', data, ...config }) }, // 更新文章 updateArticle(id, data, config = {}) { return request({ url: `/articles/${id}`, method: 'put', data, ...config }) }, // 删除文章 deleteArticle(id, config = {}) { return request({ url: `/articles/${id}`, method: 'delete', ...config }) } } export default articleApi 

src/api/index.ts:

// src/api/index.ts import userApi, { UserApi } from './modules/user' import articleApi, { ArticleApi } from './modules/article' export interface Api { user: UserApi article: ArticleApi } const api: Api = { user: userApi, article: articleApi } export default api 

请求组合式函数

我们可以创建一个组合式函数,封装常用的请求逻辑,使组件代码更加简洁。

src/composables/useRequest.ts:

// src/composables/useRequest.ts import { ref, Ref, onUnmounted } from 'vue' import { ApiResponse, RequestConfig } from '@/api/types' import { cancelAllRequests } from '@/utils/cancelToken' // 请求状态 export interface RequestState<T = any> { data: Ref<T | null> loading: Ref<boolean> error: Ref<Error | null> } // 使用请求的组合式函数 export function useRequest<T = any>( requestFn: () => Promise<ApiResponse<T>>, options: { immediate?: boolean onSuccess?: (data: T) => void onError?: (error: Error) => void config?: RequestConfig } = {} ): RequestState<T> & { run: () => Promise<void> cancel: () => void } { const { immediate = false, onSuccess, onError, config = {} } = options const data = ref<T | null>(null) const loading = ref<boolean>(false) const error = ref<Error | null>(null) const run = async () => { loading.value = true error.value = null try { const response = await requestFn() data.value = response.data onSuccess?.(response.data) } catch (err) { error.value = err as Error onError?.(err as Error) } finally { loading.value = false } } const cancel = () => { cancelAllRequests('手动取消请求') } if (immediate) { run() } // 在组件卸载时取消所有请求 onUnmounted(() => { cancel() }) return { data, loading, error, run, cancel } } // 使用分页请求的组合式函数 export function usePaginatedRequest<T = any>( requestFn: (params: any) => Promise<ApiResponse<{ data: T[] pagination: { page: number pageSize: number total: number totalPages: number } }>>, options: { immediate?: boolean onSuccess?: (data: T[], pagination: any) => void onError?: (error: Error) => void config?: RequestConfig } = {} ) { const { immediate = false, onSuccess, onError, config = {} } = options const data = ref<T[]>([]) const pagination = ref({ page: 1, pageSize: 10, total: 0, totalPages: 0 }) const loading = ref<boolean>(false) const error = ref<Error | null>(null) const run = async (params: any = {}) => { loading.value = true error.value = null try { const response = await requestFn({ page: pagination.value.page, pageSize: pagination.value.pageSize, ...params }) data.value = response.data.data pagination.value = response.data.pagination onSuccess?.(response.data.data, response.data.pagination) } catch (err) { error.value = err as Error onError?.(err as Error) } finally { loading.value = false } } const refresh = () => { run() } const reset = () => { pagination.value = { page: 1, pageSize: 10, total: 0, totalPages: 0 } run() } const changePage = (page: number) => { pagination.value.page = page run() } const changePageSize = (pageSize: number) => { pagination.value.pageSize = pageSize pagination.value.page = 1 run() } if (immediate) { run() } // 在组件卸载时取消所有请求 onUnmounted(() => { cancelAllRequests('组件卸载,取消所有请求') }) return { data, pagination, loading, error, run, refresh, reset, changePage, changePageSize } } 

在组件中使用

现在,我们可以在组件中使用这些封装好的API和组合式函数,使代码更加简洁和可维护。

<template> <div> <h1>用户管理</h1> <div class="actions"> <button @click="showCreateModal = true">创建用户</button> <input v-model="searchKeyword" @input="handleSearch" placeholder="搜索用户" /> </div> <!-- 用户列表 --> <div v-if="loading" class="loading">加载中...</div> <div v-else-if="error" class="error">{{ error.message }}</div> <div v-else> <table> <thead> <tr> <th>ID</th> <th>姓名</th> <th>邮箱</th> <th>创建时间</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="user in data" :key="user.id"> <td>{{ user.id }}</td> <td>{{ user.name }}</td> <td>{{ user.email }}</td> <td>{{ formatDate(user.createdAt) }}</td> <td> <button @click="editUser(user)">编辑</button> <button @click="deleteUser(user.id)">删除</button> </td> </tr> </tbody> </table> <!-- 分页 --> <div class="pagination"> <button @click="changePage(pagination.page - 1)" :disabled="pagination.page <= 1" > 上一页 </button> <span>第 {{ pagination.page }} 页 / 共 {{ pagination.totalPages }} 页</span> <button @click="changePage(pagination.page + 1)" :disabled="pagination.page >= pagination.totalPages" > 下一页 </button> <select v-model="pagination.pageSize" @change="changePageSize(pagination.pageSize)"> <option value="10">10条/页</option> <option value="20">20条/页</option> <option value="50">50条/页</option> </select> </div> </div> <!-- 创建用户模态框 --> <div v-if="showCreateModal" class="modal"> <div class="modal-content"> <h2>创建用户</h2> <form @submit.prevent="createUser"> <div> <label>姓名</label> <input v-model="newUser.name" required /> </div> <div> <label>邮箱</label> <input v-model="newUser.email" type="email" required /> </div> <div> <label>头像</label> <input v-model="newUser.avatar" /> </div> <div class="actions"> <button type="submit">创建</button> <button type="button" @click="showCreateModal = false">取消</button> </div> </form> </div> </div> <!-- 编辑用户模态框 --> <div v-if="showEditModal" class="modal"> <div class="modal-content"> <h2>编辑用户</h2> <form @submit.prevent="updateUser"> <div> <label>姓名</label> <input v-model="editingUser.name" required /> </div> <div> <label>邮箱</label> <input v-model="editingUser.email" type="email" required /> </div> <div> <label>头像</label> <input v-model="editingUser.avatar" /> </div> <div class="actions"> <button type="submit">更新</button> <button type="button" @click="showEditModal = false">取消</button> </div> </form> </div> </div> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue' import { usePaginatedRequest } from '@/composables/useRequest' import { throttle } from '@/utils/throttle' import api from '@/api' import { User } from '@/api/types' export default defineComponent({ setup() { // 搜索关键词 const searchKeyword = ref('') // 使用分页请求组合式函数 const { data, pagination, loading, error, run, refresh, changePage, changePageSize } = usePaginatedRequest<User>( (params) => api.user.getUsers(params), { immediate: true, onSuccess: (users, pag) => { console.log('获取用户列表成功:', users, pag) }, onError: (err) => { console.error('获取用户列表失败:', err) } } ) // 创建用户相关 const showCreateModal = ref(false) const newUser = ref({ name: '', email: '', avatar: '' }) const createUser = async () => { try { await api.user.createUser(newUser.value) showCreateModal.value = false newUser.value = { name: '', email: '', avatar: '' } refresh() } catch (err) { console.error('创建用户失败:', err) } } // 编辑用户相关 const showEditModal = ref(false) const editingUser = ref<User>({ id: 0, name: '', email: '', avatar: '', createdAt: '', updatedAt: '' }) const editUser = (user: User) => { editingUser.value = { ...user } showEditModal.value = true } const updateUser = async () => { try { await api.user.updateUser(editingUser.value.id, editingUser.value) showEditModal.value = false refresh() } catch (err) { console.error('更新用户失败:', err) } } // 删除用户 const deleteUser = async (id: number) => { if (confirm('确定要删除该用户吗?')) { try { await api.user.deleteUser(id) refresh() } catch (err) { console.error('删除用户失败:', err) } } } // 搜索处理(节流) const handleSearch = throttle(() => { run({ keyword: searchKeyword.value }) }, 300) // 格式化日期 const formatDate = (dateString: string) => { return new Date(dateString).toLocaleString() } return { searchKeyword, data, pagination, loading, error, changePage, changePageSize, showCreateModal, newUser, createUser, showEditModal, editingUser, editUser, updateUser, deleteUser, handleSearch, formatDate } } }) </script> <style scoped> .actions { margin-bottom: 20px; display: flex; gap: 10px; } table { width: 100%; border-collapse: collapse; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } .pagination { margin-top: 20px; display: flex; align-items: center; gap: 10px; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; } .modal-content { background-color: white; padding: 20px; border-radius: 5px; width: 500px; } form div { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; box-sizing: border-box; } .loading, .error { text-align: center; padding: 20px; } </style> 

性能优化

在实际项目中,我们还可以通过一些技巧来进一步优化请求性能。

请求合并

对于一些可以合并的请求,我们可以使用请求合并来减少HTTP请求的数量。例如,同时获取多个用户的信息。

src/utils目录下创建一个batchRequest.ts文件:

// src/utils/batchRequest.ts import axios from 'axios' // 批量请求函数 export const batchRequest = async <T = any>( requests: Array<() => Promise<T>>, options: { batchSize?: number delay?: number } = {} ): Promise<T[]> => { const { batchSize = 5, delay = 100 } = options const results: T[] = [] // 分批处理请求 for (let i = 0; i < requests.length; i += batchSize) { const batch = requests.slice(i, i + batchSize) // 发送当前批次的请求 const batchResults = await Promise.all( batch.map(request => request()) ) results.push(...batchResults) // 如果不是最后一批,则延迟一段时间 if (i + batchSize < requests.length) { await new Promise(resolve => setTimeout(resolve, delay)) } } return results } // 批量获取用户信息 export const batchGetUsers = async (userIds: number[]) => { const requests = userIds.map(id => () => { return axios.get(`/api/users/${id}`).then(response => response.data) }) return batchRequest(requests) } 

请求优先级

对于一些重要的请求,我们可以设置更高的优先级,确保它们能够优先处理。

src/utils目录下创建一个priorityRequest.ts文件:

// src/utils/priorityRequest.ts import axios from 'axios' // 请求优先级枚举 export enum RequestPriority { HIGH = 'high', MEDIUM = 'medium', LOW = 'low' } // 优先级队列 class PriorityQueue<T> { private items: { value: T; priority: RequestPriority }[] = [] enqueue(value: T, priority: RequestPriority = RequestPriority.MEDIUM): void { this.items.push({ value, priority }) this.sort() } dequeue(): T | undefined { return this.items.shift()?.value } isEmpty(): boolean { return this.items.length === 0 } size(): number { return this.items.length } private sort(): void { this.items.sort((a, b) => { const priorityOrder = { [RequestPriority.HIGH]: 3, [RequestPriority.MEDIUM]: 2, [RequestPriority.LOW]: 1 } return priorityOrder[b.priority] - priorityOrder[a.priority] }) } } // 请求管理器 class RequestManager { private queue = new PriorityQueue<() => Promise<any>>() private isProcessing = false private maxConcurrent = 3 private currentConcurrent = 0 async addRequest( requestFn: () => Promise<any>, priority: RequestPriority = RequestPriority.MEDIUM ): Promise<any> { return new Promise((resolve, reject) => { const wrappedRequest = async () => { try { const result = await requestFn() resolve(result) } catch (error) { reject(error) } finally { this.currentConcurrent-- this.processQueue() } } this.queue.enqueue(wrappedRequest, priority) this.processQueue() }) } private async processQueue(): Promise<void> { if (this.isProcessing || this.currentConcurrent >= this.maxConcurrent) { return } this.isProcessing = true while (!this.queue.isEmpty() && this.currentConcurrent < this.maxConcurrent) { const request = this.queue.dequeue() if (request) { this.currentConcurrent++ request() } } this.isProcessing = false } } // 创建请求管理器实例 const requestManager = new RequestManager() // 优先级请求函数 export const priorityRequest = async <T = any>( requestFn: () => Promise<T>, priority: RequestPriority = RequestPriority.MEDIUM ): Promise<T> => { return requestManager.addRequest(requestFn, priority) } 

请求缓存策略

对于一些不经常变化的数据,我们可以使用更复杂的缓存策略,例如 stale-while-revalidate。

src/utils目录下创建一个swrCache.ts文件:

// src/utils/swrCache.ts // 缓存项接口 interface CacheItem<T = any> { data: T timestamp: number isValidating: boolean } // SWR缓存类 class SWRCache { private cache = new Map<string, CacheItem>() private defaultTTL = 60 * 1000 // 默认缓存时间:60秒 // 获取缓存数据 get<T = any>(key: string): T | null { const item = this.cache.get(key) if (!item) { return null } // 如果缓存过期,则返回null if (Date.now() - item.timestamp > this.defaultTTL) { return null } return item.data } // 设置缓存数据 set<T = any>(key: string, data: T, ttl: number = this.defaultTTL): void { this.cache.set(key, { data, timestamp: Date.now(), isValidating: false }) } // 检查缓存是否存在 has(key: string): boolean { return this.cache.has(key) } // 删除缓存 delete(key: string): boolean { return this.cache.delete(key) } // 清空所有缓存 clear(): void { this.cache.clear() } // 标记缓存正在验证 markValidating(key: string): void { const item = this.cache.get(key) if (item) { item.isValidating = true } } // 取消标记缓存正在验证 unmarkValidating(key: string): void { const item = this.cache.get(key) if (item) { item.isValidating = false } } // 检查缓存是否正在验证 isValidating(key: string): boolean { const item = this.cache.get(key) return item ? item.isValidating : false } } // 创建缓存实例 const swrCache = new SWRCache() // SWR请求函数 export const swrRequest = async <T = any>( key: string, requestFn: () => Promise<T>, options: { ttl?: number revalidateOnFocus?: boolean revalidateOnReconnect?: boolean } = {} ): Promise<T> => { const { ttl = 60 * 1000, revalidateOnFocus = true, revalidateOnReconnect = true } = options // 如果缓存存在且未过期,直接返回缓存数据 const cachedData = swrCache.get<T>(key) if (cachedData && !swrCache.isValidating(key)) { // 在后台重新验证数据 swrCache.markValidating(key) requestFn() .then(data => { swrCache.set(key, data, ttl) }) .catch(error => { console.error('SWR重新验证失败:', error) }) .finally(() => { swrCache.unmarkValidating(key) }) return cachedData } // 如果缓存不存在或已过期,发送请求 try { const data = await requestFn() swrCache.set(key, data, ttl) return data } catch (error) { // 如果请求失败,但缓存存在,则返回缓存数据 if (swrCache.has(key)) { const cachedData = swrCache.get<T>(key) if (cachedData) { return cachedData } } throw error } } // 监听页面可见性变化,重新验证数据 if (typeof window !== 'undefined') { window.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { // 页面变为可见时,重新验证所有缓存 // 这里可以根据实际需求实现 } }) window.addEventListener('online', () => { // 网络恢复连接时,重新验证所有缓存 // 这里可以根据实际需求实现 }) } 

通过这些优化技巧,我们可以进一步提高应用的性能和用户体验。

总结

本文详细介绍了Vue3与Axios的完美集成方法,从基础配置到高级应用,涵盖了数据请求处理的各个方面。通过合理地封装请求、使用拦截器、处理错误、取消请求、优化请求等技巧,我们可以构建出高效、可维护的前端应用。

在实际项目中,我们应该根据具体需求选择合适的技术和策略,不断优化和完善数据请求层,提高开发效率和用户体验。希望本文能够帮助开发者更好地理解和应用Vue3与Axios的集成,提升前端开发水平。