第81集:Axios封装与配置
概述
Axios是Vue生态中最流行的HTTP客户端库,用于发送HTTP请求。在大型Vue应用中,对Axios进行合理的封装和配置可以提高代码的可维护性、可扩展性和性能。掌握Axios的封装和配置对于构建高效、可靠的API通信层至关重要。
核心知识点
1. Axios基础
1.1 安装Axios
npm install axios
# 或
yarn add axios
# 或
pnpm add axios1.2 基本使用
// 简单使用
import axios from 'axios'
async function fetchUsers() {
try {
const response = await axios.get('https://api.example.com/users')
console.log(response.data)
} catch (error) {
console.error('Error fetching users:', error)
}
}
// 发送POST请求
async function createUser(user: User) {
try {
const response = await axios.post('https://api.example.com/users', user)
console.log(response.data)
} catch (error) {
console.error('Error creating user:', error)
}
}2. Axios封装设计
2.1 为什么需要封装Axios
- 统一API请求配置
- 集中处理请求和响应拦截
- 统一错误处理
- 简化API调用
- 支持多环境配置
- 便于扩展和维护
2.2 封装Axios实例
// src/utils/axios.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
class HttpRequest {
private instance: AxiosInstance
constructor() {
// 创建Axios实例
this.instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 初始化拦截器
this.initInterceptors()
}
// 初始化拦截器
private initInterceptors(): void {
// 请求拦截器
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 添加请求拦截逻辑,如添加token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
// 统一处理响应数据
const { data, status } = response
if (status === 200) {
return data
} else {
return Promise.reject(new Error('Request failed'))
}
},
(error) => {
// 统一处理错误
return Promise.reject(error)
}
)
}
// 请求方法封装
request<T = any>(config: AxiosRequestConfig): Promise<T> {
return new Promise((resolve, reject) => {
this.instance
.request<any, T>(config)
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}
// GET请求
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'GET', url })
}
// POST请求
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'POST', url, data })
}
// PUT请求
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'PUT', url, data })
}
// DELETE请求
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE', url })
}
// PATCH请求
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH', url, data })
}
}
// 导出Axios实例
export const http = new HttpRequest()3. 环境配置
3.1 Vite环境变量
在Vite项目中,可以使用.env文件配置环境变量:
# .env.development
VITE_API_BASE_URL = http://localhost:3000/api
# .env.production
VITE_API_BASE_URL = https://api.example.com/api
# .env.test
VITE_API_BASE_URL = https://test-api.example.com/api3.2 动态环境配置
// src/utils/axios.ts
// 根据环境动态设置baseURL
const getBaseUrl = () => {
switch (import.meta.env.MODE) {
case 'development':
return 'http://localhost:3000/api'
case 'production':
return 'https://api.example.com/api'
case 'test':
return 'https://test-api.example.com/api'
default:
return '/api'
}
}
class HttpRequest {
private instance: AxiosInstance
constructor() {
this.instance = axios.create({
baseURL: getBaseUrl(),
// ...
})
// ...
}
// ...
}4. API模块化设计
4.1 按功能划分API模块
src/api/
├── index.ts # 统一出口
├── auth.ts # 认证相关API
├── user.ts # 用户管理API
├── product.ts # 产品管理API
└── order.ts # 订单管理API4.2 API模块示例
// src/api/user.ts
import { http } from '../utils/axios'
import type { User, UserListParams, UserListResponse } from '../types/user'
// 获取用户列表
export const getUserList = (params: UserListParams) => {
return http.get<UserListResponse>('/users', { params })
}
// 获取用户详情
export const getUserById = (id: number) => {
return http.get<User>(`/users/${id}`)
}
// 创建用户
export const createUser = (data: User) => {
return http.post<User>('/users', data)
}
// 更新用户
export const updateUser = (id: number, data: Partial<User>) => {
return http.put<User>(`/users/${id}`, data)
}
// 删除用户
export const deleteUser = (id: number) => {
return http.delete(`/users/${id}`)
}4.3 统一API出口
// src/api/index.ts
export * from './auth'
export * from './user'
export * from './product'
export * from './order'5. 类型定义
5.1 API响应类型
// src/types/api.ts
export interface ApiResponse<T = any> {
code: number
message: string
data: T
success: boolean
}
// src/types/user.ts
export interface User {
id: number
name: string
email: string
createdAt: string
updatedAt: string
}
export interface UserListParams {
page: number
limit: number
keyword?: string
}
export interface UserListResponse {
list: User[]
total: number
page: number
limit: number
}5.2 Axios实例类型扩展
// src/types/axios.d.ts
import 'axios'
declare module 'axios' {
export interface AxiosInstance {
request<T = any>(config: AxiosRequestConfig): Promise<T>
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
}
}6. 高级配置
6.1 超时处理
// src/utils/axios.ts
class HttpRequest {
constructor() {
this.instance = axios.create({
timeout: 10000, // 设置全局超时时间
// ...
})
// ...
}
// ...
}
// 单独设置超时时间
export const fetchLargeData = () => {
return http.get('/large-data', { timeout: 30000 }) // 30秒超时
}6.2 取消请求
// src/utils/axios.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource } from 'axios'
class HttpRequest {
private instance: AxiosInstance
private cancelTokenSourceMap: Map<string, CancelTokenSource> = new Map()
constructor() {
// ...
}
// 取消请求
cancelRequest(url: string) {
if (this.cancelTokenSourceMap.has(url)) {
this.cancelTokenSourceMap.get(url)?.cancel()
this.cancelTokenSourceMap.delete(url)
}
}
// 取消所有请求
cancelAllRequests() {
this.cancelTokenSourceMap.forEach((source) => {
source.cancel()
})
this.cancelTokenSourceMap.clear()
}
// 请求方法封装
request<T = any>(config: AxiosRequestConfig): Promise<T> {
const url = config.url || ''
// 创建取消令牌
const source = axios.CancelToken.source()
this.cancelTokenSourceMap.set(url, source)
config.cancelToken = source.token
return new Promise((resolve, reject) => {
this.instance
.request<any, T>(config)
.then((response) => {
this.cancelTokenSourceMap.delete(url)
resolve(response)
})
.catch((error) => {
this.cancelTokenSourceMap.delete(url)
reject(error)
})
})
}
// ...
}6.3 重试机制
// src/utils/axios.ts
class HttpRequest {
// 添加重试逻辑的请求方法
requestWithRetry<T = any>(config: AxiosRequestConfig, retryCount: number = 3): Promise<T> {
return new Promise((resolve, reject) => {
let attempt = 0
const attemptRequest = () => {
attempt++
this.instance
.request<any, T>(config)
.then((response) => {
resolve(response)
})
.catch((error) => {
if (attempt < retryCount) {
// 延迟重试
setTimeout(attemptRequest, 1000 * attempt)
} else {
reject(error)
}
})
}
attemptRequest()
})
}
// ...
}最佳实践
1. 统一错误处理
// src/utils/axios.ts
class HttpRequest {
constructor() {
// ...
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
// ...
},
(error) => {
// 统一错误处理
const { response } = error
if (response) {
switch (response.status) {
case 400:
console.error('请求错误:', response.data.message)
break
case 401:
console.error('未授权,请重新登录')
// 跳转到登录页面
// router.push('/login')
break
case 403:
console.error('拒绝访问')
break
case 404:
console.error('请求地址不存在')
break
case 500:
console.error('服务器内部错误')
break
default:
console.error('请求失败')
}
} else if (error.request) {
console.error('网络错误,请检查网络连接')
} else {
console.error('请求配置错误')
}
return Promise.reject(error)
}
)
}
// ...
}2. 加载状态管理
// src/utils/axios.ts
class HttpRequest {
private pendingRequests: Set<string> = new Set()
constructor() {
// ...
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
const url = config.url || ''
this.pendingRequests.add(url)
// 可以在这里触发全局加载状态
// eventBus.emit('loading', true)
return config
},
(error) => {
return Promise.reject(error)
}
)
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
const url = response.config.url || ''
this.pendingRequests.delete(url)
if (this.pendingRequests.size === 0) {
// 可以在这里关闭全局加载状态
// eventBus.emit('loading', false)
}
return response
},
(error) => {
if (error.config) {
const url = error.config.url || ''
this.pendingRequests.delete(url)
if (this.pendingRequests.size === 0) {
// 可以在这里关闭全局加载状态
// eventBus.emit('loading', false)
}
}
return Promise.reject(error)
}
)
}
// ...
}3. API缓存
// src/utils/axios.ts
class HttpRequest {
private cache: Map<string, { data: any; timestamp: number }> = new Map()
private cacheExpireTime = 5 * 60 * 1000 // 5分钟缓存
// 带缓存的GET请求
getWithCache<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
const cacheKey = `${url}_${JSON.stringify(config?.params)}`
const cachedData = this.cache.get(cacheKey)
// 检查缓存是否有效
if (cachedData && Date.now() - cachedData.timestamp < this.cacheExpireTime) {
return Promise.resolve(cachedData.data as T)
}
// 缓存无效,重新请求
return this.get<T>(url, config).then((data) => {
// 更新缓存
this.cache.set(cacheKey, { data, timestamp: Date.now() })
return data
})
}
// 清除缓存
clearCache(url?: string) {
if (url) {
// 清除指定URL的缓存
this.cache.forEach((_, key) => {
if (key.startsWith(url)) {
this.cache.delete(key)
}
})
} else {
// 清除所有缓存
this.cache.clear()
}
}
// ...
}常见问题与解决方案
1. 跨域问题
问题:浏览器出现跨域错误。
解决方案:
- 配置CORS(跨域资源共享)
- 使用代理服务器
- 使用JSONP(仅支持GET请求)
在Vite项目中配置代理:
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})2. Token过期处理
问题:Token过期导致请求失败。
解决方案:
- 在响应拦截器中检测Token过期
- 自动刷新Token
- 跳转到登录页面
// src/utils/axios.ts
class HttpRequest {
private isRefreshing = false
private refreshTokenQueue: Array<(token: string) => void> = []
constructor() {
// ...
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
// ...
},
async (error) => {
const { response } = error
if (response && response.status === 401) {
if (!this.isRefreshing) {
this.isRefreshing = true
try {
// 刷新Token
const refreshToken = localStorage.getItem('refreshToken')
const { token: newToken } = await http.post('/auth/refresh', { refreshToken })
// 更新Token
localStorage.setItem('token', newToken)
// 执行队列中的请求
this.refreshTokenQueue.forEach((callback) => callback(newToken))
this.refreshTokenQueue = []
// 重试当前请求
return this.instance(error.config)
} catch (refreshError) {
// 刷新Token失败,跳转到登录页面
// router.push('/login')
return Promise.reject(refreshError)
} finally {
this.isRefreshing = false
}
} else {
// 等待Token刷新完成
return new Promise((resolve) => {
this.refreshTokenQueue.push((token: string) => {
error.config.headers.Authorization = `Bearer ${token}`
resolve(this.instance(error.config))
})
})
}
}
return Promise.reject(error)
}
)
}
// ...
}3. 重复请求问题
问题:短时间内发送多个相同请求,导致性能问题。
解决方案:
- 取消重复请求
- 使用缓存
- 使用防抖和节流
// src/utils/axios.ts
class HttpRequest {
private pendingRequests: Set<string> = new Set()
constructor() {
// ...
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
const requestKey = `${config.method}_${config.url}_${JSON.stringify(config.params)}_${JSON.stringify(config.data)}`
if (this.pendingRequests.has(requestKey)) {
return Promise.reject(new Error('Duplicate request'))
}
this.pendingRequests.add(requestKey)
return config
},
(error) => {
return Promise.reject(error)
}
)
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
const requestKey = `${response.config.method}_${response.config.url}_${JSON.stringify(response.config.params)}_${JSON.stringify(response.config.data)}`
this.pendingRequests.delete(requestKey)
return response
},
(error) => {
if (error.config) {
const requestKey = `${error.config.method}_${error.config.url}_${JSON.stringify(error.config.params)}_${JSON.stringify(error.config.data)}`
this.pendingRequests.delete(requestKey)
}
return Promise.reject(error)
}
)
}
// ...
}进一步学习资源
课后练习
基础练习:
- 安装Axios并创建Axios实例
- 配置请求拦截器和响应拦截器
- 实现基本的GET和POST请求
进阶练习:
- 封装Axios类,支持多种HTTP方法
- 实现API模块化设计
- 添加环境配置支持
- 实现TypeScript类型定义
高级练习:
- 实现取消请求功能
- 添加重试机制
- 实现API缓存
- 处理Token过期问题
实战练习:
- 集成到Vue项目中
- 实现用户管理API
- 添加加载状态管理
- 实现统一错误处理
通过本节课的学习,你应该能够掌握Axios的封装和配置,理解API模块化设计,掌握环境配置和类型定义,以及Axios的高级特性如取消请求、重试机制和缓存。这些知识将帮助你构建高效、可靠的API通信层,提高应用的性能和可维护性。