基于TypeScript的Axios封装实践方案
2026年3月21日 #typescript #封装 #请求 #axios
在项目中,合理封装请求可以提升代码复用性、统一错误处理并增强类型安全,本文将介绍一个完整的TypeScript封装方案,包含uniapp端和pc端请求拦截、响应拦截、错误处理和模块化API管理。
uniapp端请求封装(ts版)
// request.ts
const base_url = 'https://xx.com'
// ===== 类型定义 =====
interface ApiResponse<T = any> {
code: number
msg: string
data: T
}
interface RequestOptions<T = any> {
url?: string
method?: UniApp.RequestOptions['method']
data?: any
header?: Record<string, any>
loading?: boolean
showError?: boolean
showSuccess?: boolean // ✅ 新增
responseType?: UniApp.RequestOptions['responseType']
timeout?: number
}
// ===== loading 控制 =====
let loadingCount = 0
function showLoading(title = '加载中...') {
if (loadingCount === 0) {
uni.showLoading({ title, mask: true })
}
loadingCount++
}
function hideLoading() {
if (loadingCount > 0) {
loadingCount--
}
if (loadingCount === 0) {
uni.hideLoading()
}
}
// ===== 核心请求函数 =====
export function request<T = any>(options: RequestOptions<T> = {}): Promise<ApiResponse<T>> {
const {
url = '',
method = 'GET',
data = {},
header = {},
loading = false,
showError = true,
showSuccess = false,
responseType = 'text',
timeout = 15000
} = options
// token 注入
const requestHeader: Record<string, any> = { ...header }
const token = uni.getStorageSync('token')
if (token) {
requestHeader.Authorization = `Bearer ${token}`
}
if (loading) {
showLoading()
}
return new Promise((resolve, reject) => {
uni.request({
url: base_url + url,
method,
data,
header: requestHeader,
timeout,
responseType,
success: (res) => {
const { statusCode } = res
const resData = res.data as ApiResponse<T>
// HTTP 错误
if (statusCode !== 200) {
handleHttpError(statusCode || 0, showError)
reject(res)
return
}
// 业务错误
if (resData.code !== 1) {
if (showError) {
uni.showToast({
title: resData.msg || '请求失败',
icon: 'none'
})
}
if (resData.code === 401) {
handleLogout()
}
reject(resData)
return
}
// 成功提示
if (showSuccess) {
uni.showToast({
title: resData.msg || '操作成功',
icon: 'none'
})
}
resolve(resData)
},
fail: (err) => {
if (showError) {
uni.showToast({
title: '网络异常,请稍后再试',
icon: 'none'
})
}
reject(err)
},
complete: () => {
if (loading) {
hideLoading()
}
}
})
})
}
// ===== HTTP 错误处理 =====
function handleHttpError(statusCode: number, showError: boolean) {
const map: Record<number, string> = {
400: '请求错误',
401: '未授权,请重新登录',
403: '拒绝访问',
404: '接口不存在',
500: '服务器错误'
}
if (showError) {
uni.showToast({
title: map[statusCode] || '网络错误',
icon: 'none'
})
}
if (statusCode === 401) {
handleLogout()
}
}
// ===== 退出登录 =====
function handleLogout() {
uni.removeStorageSync('token')
uni.reLaunch({
url: '/pages/login/login'
})
}
使用方法:
import { request } from '@/utils/request'
const res = await request<UserInfo>({
url: '/user/info',
method: 'GET',
loading: true
})
uniapp端请求封装(js版)
const base_url = 'https://xx.com'
// ===== loading 控制 =====
let loadingCount = 0
function showLoading(title = '加载中...') {
if (loadingCount === 0) {
uni.showLoading({ title, mask: true })
}
loadingCount++
}
function hideLoading() {
if (loadingCount > 0) {
loadingCount--
}
if (loadingCount === 0) {
uni.hideLoading()
}
}
// ===== 核心请求函数 =====
export function request(options = {}) {
const {
url = '',
method = 'GET',
data = {},
header = {},
loading = false,
showError = true,
showSuccess = false,
responseType = 'text',
timeout = 15000
} = options
// token 注入
const requestHeader = { ...header }
const token = uni.getStorageSync('token')
if (token) {
requestHeader.Authorization = `Bearer ${token}`
}
if (loading) {
showLoading()
}
return new Promise((resolve, reject) => {
uni.request({
url: base_url + url,
method,
data,
header: requestHeader,
timeout,
responseType,
success: (res) => {
const statusCode = res.statusCode
const resData = res.data || {}
// HTTP 错误
if (statusCode !== 200) {
handleHttpError(statusCode || 0, showError)
reject(res)
return
}
// 业务错误
if (resData.code !== 1) {
if (showError) {
uni.showToast({
title: resData.msg || '请求失败',
icon: 'none'
})
}
if (resData.code === 401) {
handleLogout()
}
reject(resData)
return
}
// 成功提示
if (showSuccess) {
uni.showToast({
title: resData.msg || '操作成功',
icon: 'none'
})
}
resolve(resData)
},
fail: (err) => {
if (showError) {
uni.showToast({
title: '网络异常,请稍后再试',
icon: 'none'
})
}
reject(err)
},
complete: () => {
if (loading) {
hideLoading()
}
}
})
})
}
// ===== HTTP 错误处理 =====
function handleHttpError(statusCode, showError) {
const map = {
400: '请求错误',
401: '未授权,请重新登录',
403: '拒绝访问',
404: '接口不存在',
500: '服务器错误'
}
if (showError) {
uni.showToast({
title: map[statusCode] || '网络错误',
icon: 'none'
})
}
if (statusCode === 401) {
handleLogout()
}
}
// ===== 退出登录 =====
function handleLogout() {
uni.removeStorageSync('token')
uni.reLaunch({
url: '/pages/login/login'
})
}
pc端请求封装(ts版)
import axios, { type AxiosRequestConfig, type AxiosResponse, type AxiosInstance } from 'axios'
import { ElLoading, ElMessage, type LoadingInstance } from 'element-plus'
import router from '@/router'
import { useUserStore } from '@/stores/user'
/**
* Axios 实例
* baseURL 优先使用环境变量,没有则退回 /api
*/
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 15000,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
/**
* 后端统一返回结构
*/
export interface ApiResponse<T = unknown> {
data: T
msg: string
code: number
}
/**
* 扩展后的请求配置
* 仍兼容 AxiosRequestConfig
*/
export interface RequestConfig extends AxiosRequestConfig {
showSuccessMessage?: boolean
showErrorMessage?: boolean
showLoading?: boolean
}
/**
* 自定义默认行为(集中管理)
*/
const options = {
showSuccessMessage: false,
showErrorMessage: true,
showLoading: false
}
// -------- Loading 计数,避免多个请求同时闪烁 --------
let loadingInstance: LoadingInstance | null = null
let loadingCount = 0
function showLoading(title = '加载中...'): void {
if (loadingCount === 0) {
loadingInstance = ElLoading.service({
lock: true,
text: title,
background: 'rgba(0, 0, 0, 0.7)'
})
}
loadingCount += 1
}
function hideLoading(): void {
if (loadingCount > 0) {
loadingCount -= 1
}
if (loadingCount === 0) {
loadingInstance?.close()
loadingInstance = null
}
}
// -------- 请求拦截器 --------
service.interceptors.request.use(
(config) => {
const requestConfig = config as RequestConfig
// 根据 options 控制 loading
if (requestConfig.showLoading ?? options.showLoading) {
showLoading()
}
// 统一拼装请求头
const userStore = useUserStore()
if (userStore.token) {
config.headers = {
...(config.headers || {}),
Authorization: `Bearer ${userStore.token}`
}
}
return config
},
(error) => Promise.reject(error)
)
// -------- 响应拦截器 --------
service.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => {
const config = response.config as RequestConfig
if (config.showLoading ?? options.showLoading) {
hideLoading()
}
const { code, msg } = response.data
const showError = config.showErrorMessage ?? options.showErrorMessage
if (code === 1) {
if (config.showSuccessMessage ?? options.showSuccessMessage) {
ElMessage.success(msg || '操作成功')
}
return response
}
if (showError) {
ElMessage.error(msg || '请求失败')
}
if (code === 401) {
handleLogout()
}
return Promise.reject(msg || '请求失败')
},
(error) => {
const config = (error?.config || {}) as RequestConfig
if (config.showLoading ?? options.showLoading) {
hideLoading()
}
const showError = config.showErrorMessage ?? options.showErrorMessage
if (error?.response?.status) {
handleHttpError(error.response.status, showError)
} else if (showError) {
ElMessage.error('网络异常,请稍后再试')
}
return Promise.reject(error)
}
)
/**
* 核心请求函数
* 约定:后端 code === 1 才视为成功
*/
export async function request<T = unknown>(config: RequestConfig): Promise<T> {
const response = await service.request<ApiResponse<T>, AxiosResponse<ApiResponse<T>>>(config)
return response.data.data as T
}
// -------- HTTP 状态码统一处理 --------
function handleHttpError(status: number, showError: boolean): void {
const map: Record<number, string> = {
400: '请求错误',
401: '未授权,请重新登录',
403: '拒绝访问',
404: '接口不存在',
500: '服务器错误'
}
if (showError) {
ElMessage.error(map[status] || '网络错误')
}
if (status === 401) {
handleLogout()
}
}
// -------- 退出登录 --------
function handleLogout(): void {
const userStore = useUserStore()
userStore.clearAuth()
router.replace({ name: 'login' })
}
使用方法
import { request } from '@/utils/request'
export function getProfile() {
return request<{ name: string; uid: number }>({
url: '/api/user/profile',
method: 'get'
})
}
export function updateProfile(data: { name: string }) {
return request({
url: '/api/user/profile',
method: 'post',
data,
showLoading: true,
showSuccessMessage: true
})
}
pc端请求封装(js版)
import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import router from '@/router'
import { useUserStore } from '@/stores/user'
/**
* Axios 实例
*/
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 15000,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
/**
* 默认配置
*/
const options = {
showSuccessMessage: false,
showErrorMessage: true,
showLoading: false
}
// -------- Loading 控制 --------
let loadingInstance = null
let loadingCount = 0
function showLoading(title = '加载中...') {
if (loadingCount === 0) {
loadingInstance = ElLoading.service({
lock: true,
text: title,
background: 'rgba(0, 0, 0, 0.7)'
})
}
loadingCount++
}
function hideLoading() {
if (loadingCount > 0) {
loadingCount--
}
if (loadingCount === 0) {
loadingInstance && loadingInstance.close()
loadingInstance = null
}
}
// -------- 请求拦截器 --------
service.interceptors.request.use(
(config) => {
const requestConfig = config
// loading 控制
if (
requestConfig.showLoading !== undefined
? requestConfig.showLoading
: options.showLoading
) {
showLoading()
}
// token
const userStore = useUserStore()
if (userStore.token) {
config.headers = {
...(config.headers || {}),
Authorization: `Bearer ${userStore.token}`
}
}
return config
},
(error) => Promise.reject(error)
)
// -------- 响应拦截器 --------
service.interceptors.response.use(
(response) => {
const config = response.config
if (
config.showLoading !== undefined
? config.showLoading
: options.showLoading
) {
hideLoading()
}
const { code, msg } = response.data
const showError =
config.showErrorMessage !== undefined
? config.showErrorMessage
: options.showErrorMessage
// 成功
if (code === 1) {
if (
config.showSuccessMessage !== undefined
? config.showSuccessMessage
: options.showSuccessMessage
) {
ElMessage.success(msg || '操作成功')
}
return response
}
// 失败
if (showError) {
ElMessage.error(msg || '请求失败')
}
if (code === 401) {
handleLogout()
}
return Promise.reject(msg || '请求失败')
},
(error) => {
const config = error?.config || {}
if (
config.showLoading !== undefined
? config.showLoading
: options.showLoading
) {
hideLoading()
}
const showError =
config.showErrorMessage !== undefined
? config.showErrorMessage
: options.showErrorMessage
if (error?.response?.status) {
handleHttpError(error.response.status, showError)
} else if (showError) {
ElMessage.error('网络异常,请稍后再试')
}
return Promise.reject(error)
}
)
/**
* 核心请求函数
*/
export async function request(config) {
const response = await service.request(config)
return response.data.data
}
// -------- HTTP 状态码处理 --------
function handleHttpError(status, showError) {
const map = {
400: '请求错误',
401: '未授权,请重新登录',
403: '拒绝访问',
404: '接口不存在',
500: '服务器错误'
}
if (showError) {
ElMessage.error(map[status] || '网络错误')
}
if (status === 401) {
handleLogout()
}
}
// -------- 退出登录 --------
function handleLogout() {
const userStore = useUserStore()
userStore.clearAuth()
router.replace({ name: 'login' })
}