Quasar(Vue.js)中如何在一个boot文件引用另一boot创建的单例实例
Quasar Boot 文件间共享单例实例的实现方案
原代码失效的原因
跑不通根本是踩了两个boot开发的常见坑:
- ES模块的导出值快照特性:你在模块顶层声明
let oidc = null后直接export { oidc },所有import方拿到的都是模块刚加载时的null快照,哪怕后面boot执行逻辑里给oidc重新赋值了,import侧拿到的引用也不会跟着变 - 导入方式写错了:axios的boot文件里写
import oidc from "boot/oidc-service",导出来的是oidc-service里那个默认导出的boot执行函数,根本不是你要的OIDC实例
适配单例场景的改造步骤
1. 重写boot/oidc-service.js的单例导出逻辑
别直接存个变量就往外导,改成带getter的容器对象,保证不管什么时候拿实例,都能取到最新的赋值:
import { AuthService } from '../authorization/AuthService'; // 单例访问容器,通过getter实时读取内部的实例值 const oidcContainer = { get instance() { return oidc } } let oidc = null export default ({ router, store, Vue }) => { // boot执行时初始化OIDC实例 const OIDC = new AuthService(); router.beforeEach((to, from, next) => { const allowAnonymous = to.matched.some(record => record.meta.allowAnonymous) if (allowAnonymous) { next() } else { const isAuthenticated = OIDC.isAuthenticated() if (isAuthenticated) { next() } else { OIDC.signIn() } } }) Vue.prototype.$oidc = OIDC // 给内部的单例变量赋值 oidc = OIDC } // 导出容器,不要直接导裸变量 export { oidcContainer }
为啥要用getter?就是为了绕开ES模块的快照拷贝问题:getter会在你每次访问
oidcContainer.instance的时候,实时读模块作用域下当前的oidc值,永远能拿到正确的单例,不会出现拿到null的情况。
2. 调整boot/axios.js的实例引入逻辑
修正导入方式,通过容器的getter拿实时的单例:
// 用命名导入拿单例容器,别默认导入那个boot函数 import { oidcContainer } from "boot/oidc-service" import axios from 'axios' let axiosInstance = null; export default ({ app, router, store, Vue }) => { const AxiosInstance = axios.create({ baseURL: window.env.BASE_URL }) AxiosInstance.interceptors.request.use(function (config) { // 每次发请求的时候,通过getter取最新的OIDC单例 const oidc = oidcContainer.instance return oidc.getAccessToken().then(token => { config.headers.Authorization = `Bearer ${token}` return config }) }, (error) => { return Promise.reject(error) }) Vue.prototype.$axios = AxiosInstance axiosInstance = AxiosInstance } export { axiosInstance }
Boot顺序配置
你之前在quasar.config.js里的boot引入顺序是对的,Quasar会严格按照数组里的顺序逐个跑boot导出的函数:
// quasar.config.js 片段 boot: [ 'oidc-service', // 先跑OIDC的初始化,把单例赋值完成 'axios', // 再跑axios初始化,这时候容器里的单例已经有值了 ]
更省事的简化写法
如果你的AuthService初始化不需要依赖boot注入的router、store、Vue这些上下文参数,可以直接在模块顶层把单例实例化,根本不需要做容器:
// boot/oidc-service.js 简化版单例 import { AuthService } from '../authorization/AuthService'; // 模块加载的时候就实例化,天然是全局单例 export const oidc = new AuthService(); export default ({ router, store, Vue }) => { router.beforeEach((to, from, next) => { const allowAnonymous = to.matched.some(record => record.meta.allowAnonymous) if (allowAnonymous) { next() } else { const isAuthenticated = oidc.isAuthenticated() if (isAuthenticated) { next() } else { oidc.signIn() } } }) Vue.prototype.$oidc = oidc }
这种写法下,其他boot文件直接import { oidc } from 'boot/oidc-service'就能拿到单例,逻辑更简单。
注意:如果AuthService的初始化必须用到boot上下文里的参数(比如要拿router实例做登录跳转),就别用这种顶层实例化的写法,还是得用之前的getter容器方案。
内容的提问来源于stack exchange,提问作者span
相关产品推荐
相关产品推荐

