Quasar Vite Vue3 TS项目boot/axios.ts迁移与配置问询
迁移Quasar CLI到Vite:Axios与Vuex集成及拦截器配置方案
问题背景
从Quasar CLI迁移到Quasar Vite(版本^2.16.0,@quasar/app-vite ^2.0.0),在处理boot/axios.ts时遇到集成与拦截器逻辑适配问题,具体场景如下:
旧Quasar CLI环境的Axios启动文件
import { boot } from 'quasar/wrappers'; import axios, { AxiosInstance } from 'axios'; import { IRootState } from 'src/store'; const api = axios.create({ baseURL: process.env.API_URL, }); export default boot(({ app, store }) => { app.config.globalProperties.$axios = axios; app.config.globalProperties.$api = api; api.interceptors.request.use((config) => { const token = (store.state as IRootState).auth.currentUserData.accessToken; if (token) { config.headers.Authorization = token; } return config; }); // Additional interceptors... }); export { api };
新项目目录结构
src/ boot/ axios.ts store/ index.ts router/ index.ts
核心疑问
- 如何在Quasar Vite项目中正确配置
boot/axios.ts以集成Vuex store? - 在此新环境下如何处理Axios拦截器(含请求、响应拦截及token刷新逻辑)?
- 当前方案是否符合Quasar Vite的最佳实践?
已尝试操作
- 显式导入Vuex store:
import { defineBoot } from '#q-app/wrappers'; import { useStore } from 'src/store';
- 更新Axios启动文件,移除boot函数的store参数,直接访问Vuex状态:
export default defineBoot(({ app }) => { const store = useStore(); app.config.globalProperties.$axios = axios; app.config.globalProperties.$api = api; api.interceptors.request.use((config) => { const token = store.state.auth.currentUserData.accessToken; if (token) { config.headers.Authorization = token; } return config; }); });
- 尝试添加请求与响应拦截器处理API认证及token刷新逻辑,包括检测401错误、发起刷新token请求并重试原请求。
预期目标
- Axios与Store集成:实现无缝集成,确保API调用使用store中的access token;可在Vue组件全局使用
this.$axios和this.$api。 - Token刷新逻辑生效:收到401响应时自动刷新token并重试原请求;仅当refresh token无效时跳转登录页。
- 符合Quasar最佳实践。
解决方案
1. Axios与Vuex Store的正确集成
Quasar Vite中defineBoot不再自动传入store,需直接导入store实例(useStore()是Vue组件钩子,在boot文件中直接调用会导致状态获取异常)。
假设你的store/index.ts导出了store实例:
// src/store/index.ts import { createStore } from 'vuex'; import auth from './modules/auth'; export default createStore({ modules: { auth, }, }); // 导出类型用于类型提示(可选) export type IRootState = ReturnType<typeof store.state>;
修改boot/axios.ts的Store导入方式:
import { defineBoot } from '#q-app/wrappers'; import axios, { AxiosInstance } from 'axios'; import store from 'src/store'; import type { IRootState } from 'src/store'; const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, // Vite环境变量需用import.meta.env }); export default defineBoot(({ app }) => { // 挂载到全局属性,支持组件中使用this.$axios/this.$api app.config.globalProperties.$axios = axios; app.config.globalProperties.$api = api; // 请求拦截器:自动添加token api.interceptors.request.use((config) => { const rootState = store.state as IRootState; const token = rootState.auth.currentUserData.accessToken; if (token) { config.headers.Authorization = `Bearer ${token}`; // 通常需加Bearer前缀 } return config; }); }); export { api };
2. 完整拦截器实现(含Token刷新逻辑)
处理401自动刷新token,需避免重复刷新请求,用锁标记+请求队列实现:
import { defineBoot } from '#q-app/wrappers'; import axios, { AxiosInstance, AxiosError } from 'axios'; import store from 'src/store'; import router from 'src/router'; import type { IRootState } from 'src/store'; const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, }); // 标记是否正在刷新token let isRefreshing = false; // 存储等待重试的请求队列 let refreshQueue: Array<(token: string) => void> = []; export default defineBoot(({ app }) => { app.config.globalProperties.$axios = axios; app.config.globalProperties.$api = api; // 请求拦截器 api.interceptors.request.use((config) => { const rootState = store.state as IRootState; const token = rootState.auth.currentUserData.accessToken; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); }); // 响应拦截器 api.interceptors.response.use((response) => { return response; }, async (error: AxiosError) => { const originalRequest = error.config as any; const rootState = store.state as IRootState; const refreshToken = rootState.auth.currentUserData.refreshToken; // 处理401未授权,且不是刷新token的请求本身 if (error.response?.status === 401 && !originalRequest._retry && refreshToken) { if (isRefreshing) { // 正在刷新,加入队列等待 return new Promise((resolve) => { refreshQueue.push((token: string) => { originalRequest.headers.Authorization = `Bearer ${token}`; resolve(api(originalRequest)); }); }); } originalRequest._retry = true; isRefreshing = true; try { // 调用刷新token接口 const refreshResponse = await axios.post(`${import.meta.env.VITE_API_URL}/auth/refresh`, { refreshToken, }); const newAccessToken = refreshResponse.data.accessToken; const newRefreshToken = refreshResponse.data.refreshToken; // 更新store中的token await store.dispatch('auth/updateTokens', { accessToken: newAccessToken, refreshToken: newRefreshToken, }); // 重发队列中的请求 refreshQueue.forEach((cb) => cb(newAccessToken)); refreshQueue = []; // 重试原请求 originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return api(originalRequest); } catch (refreshError) { // 刷新失败,跳转登录页,清空store await store.dispatch('auth/logout'); router.push('/login'); return Promise.reject(refreshError); } finally { isRefreshing = false; } } return Promise.reject(error); }); }); export { api };
3. Quasar Vite项目Axios启动文件的最佳实践
- 环境变量:Vite环境变量需以
VITE_为前缀,使用import.meta.env.VITE_XXX访问,替代原process.env。 - Store导入:直接导入store实例而非使用
useStore(),boot文件属于应用初始化阶段,不处于Vue组件上下文,useStore()会失效。 - 全局挂载:保留
app.config.globalProperties挂载,同时推荐在组合式API中直接通过import { api } from 'src/boot/axios'使用。 - 拦截器解耦:复杂的拦截逻辑可抽离为单独工具函数(如
src/utils/auth.ts),避免boot文件过于臃肿。 - 类型支持:为Vue全局属性添加类型声明,避免组件中使用
this.$api时出现类型错误,可在src/typings/vue.d.ts中添加:
import type { AxiosInstance } from 'axios'; declare module '@vue/runtime-core' { interface ComponentCustomProperties { $axios: AxiosInstance; $api: AxiosInstance; } }
内容的提问来源于stack exchange,提问作者Bhavini Chauhan
相关产品推荐
相关产品推荐

