useRouter 必须在 setup 顶层调用,因其本质是 inject(routerKey),而 inject 仅在组件实例创建完成、存在 parent 链的上下文中有效;非 setup 环境无组件实例,inject 无法查找注入的 router 实例,返回 undefined。

因为 useRouter 本质是 Vue 的 inject 调用,而 inject 只在组件实例创建完成、依赖注入容器已就绪的上下文中才有效——setup 函数顶层正是这个上下文的起点;一旦离开 setup(比如工具函数、store action、普通 JS 模块),inject 就找不到注入的 router 实例,自然返回 undefined。
useRouter 本质是 inject(routerKey)
useRouter 并非直接导出 router 实例,而是封装了一次 inject(ROUTE_KEY)。这个 ROUTE_KEY 是 vue-router 内部定义的 Symbol,它在应用初始化时通过 app.provide(ROUTE_KEY, router) 注入到整个组件树的根提供者中。只有组件实例能访问该提供链,普通函数或模块没有组件实例,也就无法触发注入查找。
inject 的查找机制只作用于组件树
inject 的行为不是全局变量读取,而是沿组件实例的 parent 链向上搜索 provides 映射表:
- 每个组件实例初始化时,会把
provide数据存进自己的providesMap 中 - 调用
inject(key)时,从当前组件开始,逐级往上检查parent.provides.has(key) - 直到找到匹配项,或到达根组件仍无结果 → 返回
undefined
非 setup 环境(如 utils/xxx.js)根本没有组件实例,更不存在 parent 链,inject 失去查找路径,只能失败。
为什么必须在 setup 顶层?不能在内部函数里调用?
Vue 的组合式 API 依赖“调用栈上下文”来关联当前组件实例。setup 执行时,Vue 会临时将当前组件实例绑定为当前活跃实例(类似 React 的 render phase)。这个绑定仅在 setup 同步执行期间有效:
- 顶层调用
useRouter():此时活跃实例存在,inject能正确获取 - 在
onClick或自定义函数内调用:函数执行时,活跃实例可能已切换(比如异步回调、事件触发后),或根本未被 Vue 追踪 →inject无上下文可依
不依赖 setup 的替代方案
如果确实需要在非组件环境使用 router,有两类安全做法:
-
直接导入 router 实例:在
router/index.js中导出createRouter(...)创建的对象,其他地方import { router } from '@/router'—— 这是纯 ES 模块引用,不走 inject -
在 Pinia store 中注入:在 store 的
setup()或defineStore工厂函数中调用useRouter(),保存到 store state 或 actions 中,后续通过 store 访问
注意:SSR 场景下需优先选 store 注入方式,避免服务端模块缓存导致 router 实例复用异常。

















