Vue3中使用Axios完整指南
目录
简介
Axios是一个基于Promise的HTTP客户端,用于浏览器和Node.js中发送HTTP请求。在Vue3项目中,Axios是最常用的HTTP请求库之一,它提供了简洁的API和强大的功能,包括请求/响应拦截、请求取消、自动数据转换等。
本指南将从零开始,详细介绍如何在Vue3项目中使用Axios,包括基础配置、请求封装、错误处理等各个方面。
安装与配置
安装Axios
首先,需要在项目中安装Axios:
npm install axios
# 或
yarn add axios
# 或
pnpm add axios
基础配置
创建一个Axios实例,并进行基础配置:
// src/utils/request.ts
import axios from 'axios'
import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
// 扩展Axios的类型定义
declare module 'axios' {
interface InternalAxiosRequestConfig {
metadata?: {
startTime: Date
}
}
}
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'https://api.example.com', // API基础路径
timeout: 15000, // 请求超时时间
headers: {
'Content-Type': 'application/json;charset=UTF-8' // 默认内容类型
},
// 新版本Axios支持的新选项
transitional: {
silentJSONParsing: false, // 如果JSON解析失败,抛出错误而不是静默处理
forcedJSONParsing: true // 尝试解析非JSON类型的响应
},
// 启用请求/响应转换器
transformResponse: [function (data) {
try {
return JSON.parse(data)
} catch (err) {
return data
}
}]
})
export default service
Axios基础使用
直接使用Axios
在Vue3组件中,可以直接使用Axios发送请求:
<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
const userData = ref(null)
const loading = ref(false)
const fetchData = async () => {
loading.value = true
try {
// 直接使用axios发送GET请求
const response = await axios.get('https://jsonplaceholder.typicode.com/users/1')
userData.value = response.data
} catch (error) {
console.error('获取用户数据失败:', error)
} finally {
loading.value = false
}
}
</script>
<template>
<div>
<button @click="fetchData">获取用户数据</button>
<div v-if="loading">加载中...</div>
<div v-else>
<pre>{{ userData }}</pre>
</div>
</div>
</template>
封装Axios实例
为了更好的管理和复用,推荐将Axios实例封装成一个工具函数:
// src/utils/request.ts
// ... 前面的配置代码 ...
// 导出axios实例
export default service
然后在组件中使用封装好的实例:
<script setup lang="ts">
import { ref } from 'vue'
import request from '../utils/request'
const userData = ref(null)
const loading = ref(false)
const fetchData = async () => {
loading.value = true
try {
const response = await request.get('/users/1')
userData.value = response.data
} catch (error) {
console.error('获取用户数据失败:', error)
} finally {
loading.value = false
}
}
</script>
请求与响应拦截器
请求拦截器
请求拦截器可以在请求发送前对请求进行统一处理,如添加token、设置请求头等:
// src/utils/request.ts
// ... 前面的配置代码 ...
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 添加认证token
const token = localStorage.getItem('token')
if (token && config.headers) {
config.headers['Authorization'] = `Bearer ${token}`
}
// 添加请求元数据(用于计算请求耗时)
config.metadata = { startTime: new Date() }
return config
},
(error: AxiosError) => {
// 对请求错误做些什么
console.error('请求错误:', error)
return Promise.reject(error)
}
)
响应拦截器
响应拦截器可以在接收到响应后对响应数据进行统一处理,如统一错误处理、数据格式化等:
// src/utils/request.ts
// ... 前面的配置代码 ...
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
// 计算请求耗时
if (response.config.metadata) {
const endTime = new Date()
const duration = endTime.getTime() - response.config.metadata.startTime.getTime()
console.log(`请求耗时: ${duration}ms`)
}
const res = response.data
// 根据业务状态码进行处理
if (res.code !== 200) {
// 处理错误情况
console.error('响应错误:', res.message)
// 特殊状态码处理,如401表示未登录
if (res.code === 401) {
// 清除token
localStorage.removeItem('token')
// 可以添加跳转逻辑
console.log('未登录,请先登录')
}
return Promise.reject(new Error(res.message || '请求失败'))
} else {
return res
}
},
(error: AxiosError) => {
// 对响应错误做点什么
console.error('响应错误:', error)
// 处理网络错误
if (!error.response) {
console.error('网络错误,请检查网络连接')
return Promise.reject(new Error('网络错误,请检查网络连接'))
}
// 处理HTTP错误状态码
const { status } = error.response
switch (status) {
case 400:
console.error('请求参数错误')
break
case 401:
console.error('未授权,请登录')
localStorage.removeItem('token')
break
case 403:
console.error('拒绝访问')
break
case 404:
console.error('请求地址出错')
break
case 408:
console.error('请求超时')
break
case 500:
console.error('服务器内部错误')
break
default:
console.error(`请求失败,状态码: ${status}`)
}
return Promise.reject(error)
}
)
API模块化组织
为了更好的组织和管理API接口,推荐采用模块化的方式组织API:
1. 定义类型
首先,定义API相关的类型:
// src/api/types.ts
// 定义通用响应类型
export interface ResponseData<T = any> {
code: number
message: string
data: T
}
// 用户相关类型
export interface UserInfo {
id: number
name: string
email: string
phone?: string
website?: string
avatar?: string
role?: string
createTime?: string
updateTime?: string
}
export interface LoginForm {
username: string
password: string
}
// 文章相关类型
export interface Article {
id: number
title: string
content: string
category?: string
author?: string
createTime?: string
updateTime?: string
tags?: string[]
viewCount?: number
}
2. 创建API模块
按功能模块创建API文件,如用户API、文章API等:
// src/api/modules/user.ts
import service from '../../utils/request'
import type { ResponseData, UserInfo, LoginForm, RegisterForm } from '../types'
// 用户相关接口
export const userApi = {
// 获取用户信息
getUserInfo(id: string): Promise<any> {
return service({
url: '/users/' + id,
method: 'get'
})
},
// 用户登录
login(data: LoginForm): Promise<any> {
return service({
url: '/login',
method: 'post',
data
})
},
// 用户注册
register(userInfo: RegisterForm): Promise<ResponseData> {
return service({
url: '/register',
method: 'post',
data: userInfo
})
},
// 更新用户信息
updateUser(id: string, userInfo: Partial<UserInfo>): Promise<ResponseData> {
return service({
url: '/user/' + id,
method: 'put',
data: userInfo
})
},
// 获取用户列表
getUserList(params?: { page?: number; pageSize?: number; keyword?: string }): Promise<ResponseData<UserInfo[]>> {
return service({
url: '/user/list',
method: 'get',
params
})
}
}
3. 统一导出API
创建一个统一的导出文件,方便引入:
// src/api/index.ts
// 导出所有类型
export * from './types'
// 导入各个API模块
import { userApi } from './modules/user'
import { articleApi } from './modules/article'
import { uploadApi } from './modules/upload'
import { commonApi } from './modules/common'
// 统一导出所有API模块
export {
userApi,
articleApi,
uploadApi,
commonApi
}
4. 在组件中使用API
在组件中,可以方便地使用定义好的API:
<script setup lang="ts">
import { ref } from 'vue'
import { userApi } from '../api'
const userData = ref(null)
const loading = ref(false)
const fetchUserData = async () => {
loading.value = true
try {
const response = await userApi.getUserInfo('1')
userData.value = response.data
console.log('获取用户数据成功:', response)
} catch (error) {
console.error('获取用户数据失败:', error)
} finally {
loading.value = false
}
}
const handleLogin = async () => {
try {
const response = await userApi.login({
username: 'example',
password: '123456'
})
console.log('登录成功:', response)
// 处理登录逻辑...
} catch (error) {
console.error('登录失败:', error)
}
}
</script>
<template>
<div>
<button @click="fetchUserData">获取用户数据</button>
<button @click="handleLogin">登录</button>
<div v-if="loading">加载中...</div>
<div v-else>
<pre>{{ userData }}</pre>
</div>
</div>
</template>
请求取消功能
在Vue3中,可以使用Axios的AbortController API来实现请求取消功能,这对于组件卸载时取消未完成的请求、防止重复请求等场景非常有用。
基本用法
// 创建一个控制器
const controller = new AbortController()
// 获取控制器的信号
const signal = controller.signal
// 发送请求时传入signal
axios.get('/api/data', { signal })
// 取消请求
controller.abort()
组件中使用请求取消
在Vue组件中,可以在组件卸载时自动取消请求:
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import request from '../utils/取消请求.ts'
const userList = ref([])
const loading = ref(false)
// 创建一个控制器用于管理当前组件的请求
const controller = new AbortController()
const fetchUserList = async () => {
try {
loading.value = true
userList.value = await request.get('/api/users', {
signal: controller.signal // 用于取消 HTTP 请求的关键配置
})
} catch (error: any) {
if (error.name === 'CanceledError') {
console.log('获取用户列表请求被取消')
} else {
console.error('获取用户列表失败:', error)
}
} finally {
loading.value = false
}
}
// 组件卸载时取消请求
onUnmounted(() => {
controller.abort()
})
// 取消当前请求
const cancelRequest = () => {
controller.abort('用户主动取消请求')
}
</script>
<template>
<div>
<button @click="fetchUserList">获取用户列表</button>
<button @click="cancelRequest">取消请求</button>
<div v-if="loading">加载中...</div>
<ul v-else>
<li v-for="user in userList" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>
防止重复请求
在某些场景下,如搜索框输入时,需要防止用户快速连续触发多次请求,可以使用AbortController取消之前的请求:
let controller: AbortController | null = null
const search = async (keyword: string) => {
// 取消之前的请求
if (controller) {
controller.abort()
}
// 创建新的控制器
controller = new AbortController()
const response = await axios.get('/api/search', {
params: { keyword },
signal: controller.signal
})
return response.data
}
错误处理
良好的错误处理机制对于构建健壮的Web应用非常重要。Axios提供了多种错误处理方式。
基本错误处理
try {
const response = await axios.get('/api/data')
// 处理响应数据
} catch (error) {
// 基本错误处理
console.error('请求失败:', error)
}
详细错误处理
Axios提供了详细的错误信息,可以根据不同的错误类型进行针对性处理:
try {
const response = await axios.get('/api/data')
// 处理响应数据
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
// 服务器返回了响应,但状态码不在2xx范围内
console.error('服务器错误:', error.response.status, error.response.data)
} else if (error.request) {
// 请求已发出,但没有收到响应
console.error('网络错误: 未收到服务器响应')
} else {
// 设置请求时发生了错误
console.error('请求配置错误:', error.message)
}
} else {
// 非Axios错误
console.error('未知错误:', error)
}
}
统一错误处理(通过响应拦截器)
在响应拦截器中统一处理错误,可以避免在每个请求中重复编写错误处理代码:
// src/utils/request.ts
service.interceptors.response.use(
(response) => {
// 处理成功响应
return response.data
},
(error) => {
// 统一错误处理
if (error.response) {
// 服务器返回了响应,但状态码不在2xx范围内
switch (error.response.status) {
case 400:
console.error('请求参数错误')
break
case 401:
console.error('未授权,请登录')
// 可以在这里处理登录逻辑,如清除token并跳转到登录页
break
case 403:
console.error('拒绝访问')
break
case 404:
console.error('请求地址出错')
break
case 500:
console.error('服务器内部错误')
break
default:
console.error(`请求失败,状态码: ${error.response.status}`)
}
} else if (error.request) {
// 请求已发出,但没有收到响应
console.error('网络错误: 未收到服务器响应')
} else {
// 设置请求时发生了错误
console.error('请求配置错误:', error.message)
}
// 返回错误信息,让调用方也能处理
return Promise.reject(error)
}
)
文件上传
在Vue3项目中,使用Axios上传文件非常简单。可以使用FormData对象来构建文件数据。
基本文件上传
<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
const fileInput = ref<HTMLInputElement | null>(null)
const uploadResult = ref(null)
const uploading = ref(false)
const handleFileChange = (event: Event) => {
// 处理文件选择逻辑...
}
const uploadFile = async () => {
if (!fileInput.value?.files?.length) return
uploading.value = true
const file = fileInput.value.files[0]
try {
// 创建FormData对象
const formData = new FormData()
formData.append('file', file)
// 使用axios发送POST请求
const response = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data' // 指定内容类型为multipart/form-data
}
})
uploadResult.value = response.data
console.log('文件上传成功:', response)
} catch (error) {
console.error('文件上传失败:', error)
} finally {
uploading.value = false
}
}
</script>
<template>
<div>
<input type="file" @change="handleFileChange" ref="fileInput" />
<button @click="uploadFile" :disabled="uploading">上传文件</button>
<div v-if="uploading">上传中...</div>
<div v-if="uploadResult">
<h4>上传结果:</h4>
<pre>{{ uploadResult }}</pre>
</div>
</div>
</template>
封装文件上传API
可以将文件上传功能封装成API模块,方便复用:
// src/api/modules/upload.ts
import service from '../../utils/request'
import type { UploadResult } from '../types'
// 文件上传相关接口
export const uploadApi = {
// 上传头像
uploadAvatar(file: File): Promise<ResponseData<UploadResult>> {
const formData = new FormData()
formData.append('file', file)
return service({
url: '/api/upload/avatar',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
},
// 上传图片
uploadImage(file: File, params?: { folder?: string; compress?: boolean }): Promise<ResponseData<UploadResult>> {
const formData = new FormData()
formData.append('file', file)
// 添加额外参数
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
formData.append(key, value)
}
})
}
return service({
url: '/api/upload/image',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
}
常见问题解决方案
1. 跨域问题
在前端开发中,跨域是一个常见问题。可以通过以下几种方式解决:
使用代理
在Vite项目中,可以通过配置vite.config.ts来解决跨域问题:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://backend-api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
然后在代码中直接使用相对路径:
// 无需再配置baseURL
const service = axios.create({
baseURL: '/api' // 使用相对路径,会被vite代理转发
})
2. 请求超时处理
处理请求超时问题,可以设置合理的超时时间,并在超时后进行重试或提示用户:
// src/utils/request.ts
// 创建axios实例,设置超时时间
const service: AxiosInstance = axios.create({
timeout: 10000 // 10秒超时
})
// 响应拦截器中处理超时
service.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
console.error('请求超时')
// 可以在这里实现重试逻辑或提示用户
}
return Promise.reject(error)
}
)
3. 请求进度显示
对于文件上传或慢速请求,可以显示请求进度:
// 上传文件时显示进度
const uploadFileWithProgress = async (file: File) => {
const formData = new FormData()
formData.append('file', file)
return axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
if (progressEvent.total) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
console.log(`上传进度: ${percentCompleted}%`)
// 可以在这里更新UI显示上传进度
}
}
})
}
5. 并发请求控制
当需要同时发送多个请求,但需要控制并发数量时,可以使用Promise.all或第三方库如p-limit:
// 使用Promise.all控制并发
import axios from 'axios'
// 假设有10个请求要发送
const requestPromises = Array.from({ length: 10 }, (_, i) =>
axios.get(`/api/items/${i}`)
)
// 使用Promise.all发送所有请求
Promise.all(requestPromises)
.then(responses => {
// 所有请求都已完成
const results = responses.map(response => response.data)
console.log('所有请求结果:', results)
})
.catch(error => {
// 至少有一个请求失败
console.error('请求失败:', error)
})
// 使用p-limit控制并发数量
import pLimit from 'p-limit'
import axios from 'axios'
const limit = pLimit(5) // 最多5个并发
const requestPromises = Array.from({ length: 20 }, (_, i) =>
limit(() => axios.get(`/api/items/${i}`))
)
Promise.all(requestPromises)
.then(responses => {
const results = responses.map(response => response.data)
console.log('所有请求结果:', results)
})
.catch(error => {
console.error('请求失败:', error)
})
总结
本指南详细介绍了在Vue3项目中使用Axios的各个方面,从基础安装配置到高级功能实现,再到企业级最佳实践。通过合理地封装和使用Axios,可以构建出健壮、可维护的前端应用。
关键要点总结:
- 使用Axios实例封装基础配置和拦截器
- 采用模块化方式组织API接口
- 实现统一的错误处理机制
- 合理使用请求取消功能
- 处理常见的跨域、超时传等问题
希望这份指南能够帮助你在Vue3项目中更好地使用Axios,构建出高效、健壮的前端应用。
1204

被折叠的 条评论
为什么被折叠?



