这里提供几种常见的 Axios 封装方案,从简单到复杂:
方案一:基础封装(适合小型项目)
// request.js
import axios from 'axios'
// 创建实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000
})
// 请求拦截
service.interceptors.request.use(
config => {
// 添加token
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截
service.interceptors.response.use(
response => {
const res = response.data
// 根据自定义状态码处理
if (res.code !== 200) {
// 处理错误
return Promise.reject(new Error(res.message || 'Error'))
}
return res
},
error => {
// 处理HTTP错误状态
if (error.response) {
switch (error.response.status) {
case 401:
// 未授权处理
break
case 404:
// 404处理
break
default:
// 其他错误
}
}
return Promise.reject(error)
}
)
export default service
方案二:模块化封装(适合中大型项目)
// http/axios.js
import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
class AxiosService {
constructor() {
this.service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 30000,
withCredentials: true
})
this.setInterceptors()
}
setInterceptors() {
// 请求拦截
this.service.interceptors.request.use(
config => {
// 添加loading
if (config.showLoading) {
this.showLoading()
}
// 添加token
const token = this.getToken()
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
this.hideLoading()
return Promise.reject(error)
}
)
// 响应拦截
this.service.interceptors.response.use(
response => {
this.hideLoading()
const res = response.data
// 下载文件直接返回
if (response.config.responseType === 'blob') {
return res
}
// 业务状态码处理
if (res.code !== 200) {
this.handleBusinessError(res)
return Promise.reject(new Error(res.message || 'Error'))
}
return res
},
error => {
this.hideLoading()
this.handleHttpError(error)
return Promise.reject(error)
}
)
}
// 业务错误处理
handleBusinessError(res) {
Message({
message: res.message || '业务错误',
type: 'error',
duration: 5 * 1000
})
}
// HTTP错误处理
handleHttpError(error) {
if (error.response) {
const { status } = error.response
if (status === 401) {
MessageBox.confirm('登录已过期,请重新登录', '提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.clearToken()
location.reload()
})
} else {
Message({
message: error.response.data.message || `HTTP错误: ${status}`,
type: 'error',
duration: 5 * 1000
})
}
} else if (error.code === 'ECONNABORTED') {
Message({
message: '请求超时',
type: 'error',
duration: 5 * 1000
})
} else {
Message({
message: '网络错误',
type: 'error',
duration: 5 * 1000
})
}
}
// 获取token
getToken() {
return localStorage.getItem('token')
}
// 清除token
clearToken() {
localStorage.removeItem('token')
}
// 显示loading
showLoading() {
// 实现loading逻辑,比如使用Element UI的Loading
}
// 隐藏loading
hideLoading() {
// 隐藏loading
}
// 封装请求方法
request(config) {
return this.service.request(config)
}
get(url, params = {}, config = {}) {
return this.service.get(url, { params, ...config })
}
post(url, data = {}, config = {}) {
return this.service.post(url, data, config)
}
put(url, data = {}, config = {}) {
return this.service.put(url, data, config)
}
delete(url, params = {}, config = {}) {
return this.service.delete(url, { params, ...config })
}
}
export default new AxiosService()
方案三:TypeScript 封装(适合大型项目)
// http/index.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
// 定义响应数据结构
export interface ResponseData<T = any> {
code: number
data: T
message: string
success: boolean
}
// 定义请求配置
export interface RequestConfig extends AxiosRequestConfig {
showLoading?: boolean
showError?: boolean
retry?: number
retryDelay?: number
}
class HttpClient {
private instance: AxiosInstance
private retryCount: number = 3
private retryDelay: number = 1000
constructor() {
this.instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 30000,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
this.setupInterceptors()
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config: RequestConfig) => {
// 添加token
const token = this.getToken()
if (token) {
config.headers!['Authorization'] = `Bearer ${token}`
}
// 添加时间戳防止缓存
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now()
}
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse<ResponseData>) => {
const { data } = response
// 处理下载文件
if (response.config.responseType === 'blob') {
return response
}
// 业务状态码处理
if (data.code !== 200) {
return Promise.reject(new Error(data.message || '请求失败'))
}
return data.data
},
async (error) => {
const { config } = error
// 重试机制
if (config && config.retry !== undefined) {
config.retryCount = config.retryCount || 0
if (config.retryCount < this.retryCount) {
config.retryCount++
// 延迟重试
await new Promise(resolve =>
setTimeout(resolve, this.retryDelay)
)
return this.instance(config)
}
}
// 错误处理
this.handleError(error)
return Promise.reject(error)
}
)
}
private handleError(error: any) {
if (error.response) {
// 服务器返回错误状态码
const { status } = error.response
switch (status) {
case 401:
this.handleUnauthorized()
break
case 403:
console.error('没有权限')
break
case 404:
console.error('请求资源不存在')
break
case 500:
console.error('服务器错误')
break
default:
console.error(`HTTP错误: ${status}`)
}
} else if (error.request) {
// 请求发送失败
console.error('网络错误,请检查网络连接')
} else {
// 请求配置错误
console.error('请求配置错误:', error.message)
}
}
private handleUnauthorized() {
// 清除token
this.clearToken()
// 跳转到登录页
window.location.href = '/login'
}
private getToken(): string | null {
return localStorage.getItem('token')
}
private clearToken() {
localStorage.removeItem('token')
}
// 通用请求方法
public async request<T = any>(config: RequestConfig): Promise<T> {
try {
const response = await this.instance.request(config)
return response as T
} catch (error) {
return Promise.reject(error)
}
}
public async get<T = any>(
url: string,
params?: any,
config?: RequestConfig
): Promise<T> {
return this.request({
method: 'get',
url,
params,
...config
})
}
public async post<T = any>(
url: string,
data?: any,
config?: RequestConfig
): Promise<T> {
return this.request({
method: 'post',
url,
data,
...config
})
}
public async put<T = any>(
url: string,
data?: any,
config?: RequestConfig
): Promise<T> {
return this.request({
method: 'put',
url,
data,
...config
})
}
public async delete<T = any>(
url: string,
params?: any,
config?: RequestConfig
): Promise<T> {
return this.request({
method: 'delete',
url,
params,
...config
})
}
// 上传文件
public async upload<T = any>(
url: string,
file: File,
config?: RequestConfig
): Promise<T> {
const formData = new FormData()
formData.append('file', file)
return this.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
...config
})
}
// 下载文件
public async download(url: string, filename?: string): Promise<void> {
const response = await this.get(url, {
responseType: 'blob'
})
const blob = new Blob([response])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename || 'download'
link.click()
URL.revokeObjectURL(link.href)
}
}
export default new HttpClient()
方案四:API模块化管理
// api/modules/user.js
import http from '@/http'
export const userApi = {
// 登录
login(data) {
return http.post('/user/login', data)
},
// 获取用户信息
getUserInfo() {
return http.get('/user/info')
},
// 更新用户信息
updateUserInfo(data) {
return http.put('/user/info', data)
},
// 获取用户列表
getUserList(params) {
return http.get('/user/list', params, {
showLoading: true // 显示loading
})
}
}
// api/index.js
import { userApi } from './modules/user'
import { productApi } from './modules/product'
import { orderApi } from './modules/order'
export default {
user: userApi,
product: productApi,
order: orderApi
}
// 使用示例
// import api from '@/api'
// api.user.login({ username, password })
方案五:带缓存和取消请求的高级封装
// http/advanced.js
import axios from 'axios'
import qs from 'qs'
class AdvancedHttp {
constructor() {
this.instance = axios.create({/* 配置 */})
this.pendingRequests = new Map() // 存储待取消的请求
this.cache = new Map() // 缓存
this.setupInterceptors()
}
// 生成请求key
generateRequestKey(config) {
const { method, url, params, data } = config
return [method, url, qs.stringify(params), qs.stringify(data)].join('&')
}
// 添加待取消请求
addPendingRequest(config) {
const key = this.generateRequestKey(config)
config.cancelToken = new axios.CancelToken((cancel) => {
if (!this.pendingRequests.has(key)) {
this.pendingRequests.set(key, cancel)
}
})
}
// 取消重复请求
removePendingRequest(config) {
const key = this.generateRequestKey(config)
if (this.pendingRequests.has(key)) {
const cancel = this.pendingRequests.get(key)
cancel('取消重复请求')
this.pendingRequests.delete(key)
}
}
// 清除所有待取消请求
clearPendingRequests() {
this.pendingRequests.forEach(cancel => cancel('取消请求'))
this.pendingRequests.clear()
}
// 设置缓存
setCache(key, data, ttl = 60000) {
const cacheItem = {
data,
expire: Date.now() + ttl
}
this.cache.set(key, cacheItem)
}
// 获取缓存
getCache(key) {
const cacheItem = this.cache.get(key)
if (!cacheItem) return null
if (Date.now() > cacheItem.expire) {
this.cache.delete(key)
return null
}
return cacheItem.data
}
setupInterceptors() {
this.instance.interceptors.request.use(
config => {
// 取消重复请求
this.removePendingRequest(config)
this.addPendingRequest(config)
// 检查缓存
if (config.useCache && config.method === 'get') {
const key = this.generateRequestKey(config)
const cachedData = this.getCache(key)
if (cachedData) {
config.cached = true
return Promise.reject({
config,
cached: true,
data: cachedData
})
}
}
return config
},
error => Promise.reject(error)
)
this.instance.interceptors.response.use(
response => {
this.removePendingRequest(response.config)
// 设置缓存
if (response.config.cache && response.config.method === 'get') {
const key = this.generateRequestKey(response.config)
this.setCache(key, response.data)
}
return response
},
error => {
// 处理缓存命中
if (error.cached) {
return Promise.resolve(error.data)
}
if (axios.isCancel(error)) {
console.log('请求取消:', error.message)
return Promise.reject(error)
}
return Promise.reject(error)
}
)
}
}
export default new AdvancedHttp()
使用示例
// main.js或App.vue中使用
import http from './http'
// 基础使用
async function fetchData() {
try {
const data = await http.get('/api/data', { id: 1 })
console.log(data)
} catch (error) {
console.error(error)
}
}
// 带loading的请求
http.post('/api/save', formData, {
showLoading: true,
showError: true
})
// 带缓存和取消的请求
http.get('/api/list', { page: 1 }, {
useCache: true,
cacheTime: 60000 // 缓存1分钟
})
// 上传文件
http.upload('/api/upload', file, {
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
console.log(`上传进度: ${percent}%`)
}
})
根据项目规模和需求选择合适的封装方案。小项目用基础封装,大项目用TypeScript封装或高级封装。