vue3+ts封裝axios,無感刷新問題
更新時間:2025年12月06日 08:56:17 作者:音洛
文章詳細(xì)介紹了前端項目開發(fā)中常見的技術(shù)棧,包括安裝依賴、創(chuàng)建類型定義、狀態(tài)管理、核心封裝(Axios請求)、API接口示例、組件使用以及可選的路由守衛(wèi),作者分享了個人經(jīng)驗,希望為開發(fā)者提供參考和幫助
1. 安裝依賴
npm install axios
2. 創(chuàng)建類型定義
// types/api.ts
// 接口返回數(shù)據(jù)的統(tǒng)一格式
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}
// 請求配置
export interface RequestConfig {
showError?: boolean; // 是否顯示錯誤信息
withToken?: boolean; // 是否攜帶token
}
3. 創(chuàng)建狀態(tài)管理
// stores/auth.ts
import { ref } from 'vue'
// 簡單的響應(yīng)式狀態(tài)管理
export const useAuthStore = () => {
const token = ref(localStorage.getItem('token') || '')
const refreshToken = ref(localStorage.getItem('refreshToken') || '')
const isRefreshing = ref(false) // 是否正在刷新token
// 設(shè)置token
const setToken = (newToken: string, newRefreshToken: string) => {
token.value = newToken
refreshToken.value = newRefreshToken
localStorage.setItem('token', newToken)
localStorage.setItem('refreshToken', newRefreshToken)
}
// 清除token
const clearToken = () => {
token.value = ''
refreshToken.value = ''
localStorage.removeItem('token')
localStorage.removeItem('refreshToken')
}
return {
token,
refreshToken,
isRefreshing,
setToken,
clearToken
}
}
4. 核心封裝 - Axios 請求
// utils/request.ts
import axios from 'axios'
import type { ApiResponse, RequestConfig } from '@/types/api'
import { useAuthStore } from '@/stores/auth'
// 創(chuàng)建axios實例
const request = axios.create({
baseURL: '/api', // 你的API地址
timeout: 10000, // 10秒超時
})
// 存儲等待的請求
let waitingRequests: (() => void)[] = []
// 請求攔截器
request.interceptors.request.use(
(config) => {
const authStore = useAuthStore()
const requestConfig = config as any
// 如果配置需要token且存在token,添加到header
if (requestConfig.withToken !== false && authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 響應(yīng)攔截器
request.interceptors.response.use(
(response) => {
// 直接返回數(shù)據(jù)
return response
},
async (error) => {
const { config, response } = error
// 如果是401錯誤(token過期)
if (response?.status === 401 && config) {
return handleTokenExpired(config)
}
// 其他錯誤
handleError(error)
return Promise.reject(error)
}
)
// 處理token過期
async function handleTokenExpired(originalConfig: any): Promise<any> {
const authStore = useAuthStore()
// 如果已經(jīng)在刷新token,將請求加入等待隊列
if (authStore.isRefreshing) {
return new Promise((resolve) => {
waitingRequests.push(() => {
originalConfig.headers.Authorization = `Bearer ${authStore.token}`
resolve(request(originalConfig))
})
})
}
// 開始刷新token
authStore.isRefreshing = true
try {
// 調(diào)用刷新token接口
const refreshResponse = await request.post('/auth/refresh', {
refreshToken: authStore.refreshToken
}, { withToken: false })
const { token: newToken, refreshToken: newRefreshToken } = refreshResponse.data.data
// 更新token
authStore.setToken(newToken, newRefreshToken)
authStore.isRefreshing = false
// 重試所有等待的請求
waitingRequests.forEach(callback => callback())
waitingRequests = []
// 重試原始請求
originalConfig.headers.Authorization = `Bearer ${newToken}`
return request(originalConfig)
} catch (error) {
// 刷新token失敗,跳轉(zhuǎn)到登錄頁
authStore.clearToken()
authStore.isRefreshing = false
waitingRequests = []
// 跳轉(zhuǎn)到登錄頁
window.location.href = '/login'
return Promise.reject(error)
}
}
// 處理錯誤
function handleError(error: any) {
if (error.response) {
// 服務(wù)器返回錯誤
const { status, data } = error.response
switch (status) {
case 400:
console.error('請求參數(shù)錯誤:', data.message)
break
case 403:
console.error('沒有權(quán)限:', data.message)
break
case 404:
console.error('請求地址不存在:', data.message)
break
case 500:
console.error('服務(wù)器錯誤:', data.message)
break
default:
console.error('請求錯誤:', data.message)
}
} else if (error.request) {
// 網(wǎng)絡(luò)錯誤
console.error('網(wǎng)絡(luò)錯誤,請檢查網(wǎng)絡(luò)連接')
} else {
// 其他錯誤
console.error('請求配置錯誤:', error.message)
}
}
// 封裝常用的請求方法
export const http = {
// GET 請求
get: <T = any>(url: string, config?: RequestConfig): Promise<ApiResponse<T>> => {
return request.get(url, config).then(res => res.data)
},
// POST 請求
post: <T = any>(url: string, data?: any, config?: RequestConfig): Promise<ApiResponse<T>> => {
return request.post(url, data, config).then(res => res.data)
},
// PUT 請求
put: <T = any>(url: string, data?: any, config?: RequestConfig): Promise<ApiResponse<T>> => {
return request.put(url, data, config).then(res => res.data)
},
// DELETE 請求
delete: <T = any>(url: string, config?: RequestConfig): Promise<ApiResponse<T>> => {
return request.delete(url, config).then(res => res.data)
}
}
export default request
5. API 接口示例
// api/user.ts
import { http } from '@/utils/request'
// 用戶相關(guān)接口
export const userApi = {
// 登錄
login: (username: string, password: string) => {
return http.post<{ token: string; refreshToken: string }>('/user/login', {
username,
password
}, { withToken: false }) // 登錄接口不需要token
},
// 獲取用戶信息
getUserInfo: () => {
return http.get<{ name: string; email: string }>('/user/info')
// 默認(rèn)withToken為true,會自動攜帶token
},
// 更新用戶信息
updateUserInfo: (data: { name?: string; email?: string }) => {
return http.put('/user/info', data)
}
}
// 商品相關(guān)接口
export const productApi = {
getList: (page: number = 1, size: number = 10) => {
return http.get<{ list: any[]; total: number }>('/products', {
params: { page, size }
})
},
getDetail: (id: number) => {
return http.get(`/products/${id}`)
}
}
6. 在組件中使用
<template>
<div>
<h2>用戶信息</h2>
<div v-if="loading">加載中...</div>
<div v-else-if="userInfo">
<p>姓名: {{ userInfo.name }}</p>
<p>郵箱: {{ userInfo.email }}</p>
</div>
<div v-else>加載失敗</div>
<button @click="handleLogin">登錄</button>
<button @click="handleLogout">退出</button>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { userApi } from '@/api/user'
import { useAuthStore } from '@/stores/auth'
const loading = ref(false)
const userInfo = ref<{ name: string; email: string } | null>(null)
const authStore = useAuthStore()
// 加載用戶信息
const loadUserInfo = async () => {
try {
loading.value = true
const response = await userApi.getUserInfo()
userInfo.value = response.data
} catch (error) {
console.error('獲取用戶信息失敗')
} finally {
loading.value = false
}
}
// 登錄
const handleLogin = async () => {
try {
const response = await userApi.login('admin', '123456')
// 保存token
authStore.setToken(response.data.token, response.data.refreshToken)
// 重新加載用戶信息
loadUserInfo()
} catch (error) {
console.error('登錄失敗')
}
}
// 退出
const handleLogout = () => {
authStore.clearToken()
userInfo.value = null
}
onMounted(() => {
// 如果有token,加載用戶信息
if (authStore.token) {
loadUserInfo()
}
})
</script>
7. 路由守衛(wèi)(可選)
// router/guards.ts
import { useAuthStore } from '@/stores/auth'
export const authGuard = (to: any, from: any, next: any) => {
const authStore = useAuthStore()
// 檢查是否需要登錄
if (to.meta.requiresAuth) {
if (authStore.token) {
next() // 已登錄,允許訪問
} else {
next('/login') // 未登錄,跳轉(zhuǎn)到登錄頁
}
} else {
next() // 不需要登錄,直接訪問
}
}
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mint UI組件庫CheckList使用及踩坑總結(jié)
這篇文章主要介紹了Mint UI組件庫CheckList使用及踩坑總結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
vue3實現(xiàn)無縫滾動列表效果(大屏數(shù)據(jù)輪播場景)
vue3-scroll-seamless 是一個用于 Vue 3 的插件,用于實現(xiàn)無縫滾動的組件,它可以讓內(nèi)容在水平或垂直方向上無縫滾動,適用于展示輪播圖、新聞滾動、圖片展示等場景,本文就給大家介紹了vue3實現(xiàn)無縫滾動列表效果,需要的朋友可以參考下2024-07-07
vue學(xué)習(xí)筆記之Vue中css動畫原理簡單示例
這篇文章主要介紹了vue學(xué)習(xí)筆記之Vue中css動畫原理,結(jié)合簡單實例形式分析了Vue中css樣式變換動畫效果實現(xiàn)原理與相關(guān)操作技巧,需要的朋友可以參考下2020-02-02

