Vue3中vue-router守卫内调用useStore、usePuex返回undefined的解决方法
核心问题根因
useStore、usePuex这类依赖inject的组合式函数,本质上是顺着当前执行上下文的组件父链,读取通过provide注册的实例值。
而vue-router的路由逻辑运行在独立的模块作用域中,既不在组件的依赖注入链上,不少守卫的触发时机还早于组件挂载阶段,这个时候根本没有可供inject检索的应用上下文,自然就会返回undefined。
可落地的解决方案
方案1:改造组合式函数,增加模块级全局实例缓存
这是通用性最强的方案,既不影响组件内正常使用依赖注入的逻辑,也能让这些组合式函数在路由守卫这类非组件上下文中正常工作。
实现逻辑很简单:在模块作用域内维护一个全局实例变量,在应用层注册provide的时候,同步把实例存到这个全局变量里。改造后的useXxx函数会优先尝试通过inject读取组件上下文的实例,如果拿不到(比如在路由守卫这类非组件环境),就直接返回全局缓存的实例。
举个usePuex的改造示例:
// 自定义puex模块的公共实现 const puexKey = Symbol('puex') // 模块作用域的全局实例缓存 let cachedPuex = null // 替换原来main.js里直接app.provide的注册方法 export function registerPuex(app, instance) { app.provide(puexKey, instance) // 同步写入全局缓存 cachedPuex = instance } // 改造后的usePuex,任意上下文都能正常使用 export function usePuex() { // 组件上下文内优先走原有inject逻辑 try { const injected = inject(puexKey) if (injected) return injected } catch (e) { // 非组件上下文调用inject会直接抛错,自动走全局缓存逻辑 } return cachedPuex }
如果是用Pinia的场景就更简单,Pinia本身就内置了全局实例缓存,只要在初始化阶段调用setActivePinia(piniaInstance)把创建好的pinia实例标记为激活状态,路由守卫里的useStore就不会返回undefined,不需要额外改造业务代码。
方案2:初始化阶段显式向路由模块传递依赖实例
如果不想改动原有组合式函数的实现逻辑,可以在路由模块内暴露依赖注册入口,等所有应用层实例初始化完成后,再把实例传给路由模块,守卫内直接使用存好的实例引用即可。这套实现零侵入原有业务代码,很适合存量项目快速适配。
示例代码:
// router/index.js const router = createRouter({ /* 原有路由配置 */ }) // 存储从应用层传入的实例 let storeInstance = null let puexInstance = null // 暴露注册方法,供main.js调用 export function registerRouterDeps(store, puex) { storeInstance = store puexInstance = puex } // 路由守卫内直接使用缓存的实例 router.afterEach((to, from) => { console.log('store: ', storeInstance) puexInstance.isReady().then(() => { const me = puexInstance.me.compute() watch(me, (...params) => console.log('router: ', ...params)) }) })
对应的main.js只要调整初始化顺序即可:
// main.js import { createApp } from 'vue' import { createPinia } from 'pinia' import router, { registerRouterDeps } from './router' import { createPuex } from './puex' import App from './App.vue' const app = createApp(App) // 先初始化所有依赖实例 const pinia = createPinia() const puex = createPuex() // 把实例注册给路由模块 registerRouterDeps(pinia, puex) // 再挂载所有插件 app.use(pinia) app.use(puex) app.use(router) app.mount('#app')
常见注意事项
- 不要在路由模块的顶层作用域直接调用
useStore()、usePuex()。这个阶段整个应用还没完成初始化,哪怕做了全局缓存,对应的实例也还没创建,依然会拿到undefined。 - 路由守卫内调用
watch时,因为没有组件的自动销毁机制,需要在路由跳转、守卫触发时手动调用watch返回的stop方法清理监听,避免内存泄漏:router.afterEach((to, from) => { let stopWatcher = null puexInstance.isReady().then(() => { const me = puexInstance.me.compute() stopWatcher = watch(me, (...params) => console.log('router: ', ...params)) }) // 下次路由触发时清理上一次的watch,避免重复监听 if (stopWatcher) stopWatcher() })
内容的提问来源于stack exchange,提问作者ConductedClever
相关产品推荐
相关产品推荐

