Vue3中如何在Vue实例外编程式监听vue-router路由变化
Vue2 中$watch方法挂载在Vue构造函数原型上,所有new Vue()生成的实例(包括根应用实例)都自带该方法;Vue3 做了架构分层,createApp()返回的应用实例和组件实例是完全独立的两类对象,$watch仅存在于组件实例上(即组件内this指向的对象、getCurrentInstance()返回的代理对象),应用实例本身不提供组件级方法,因此你在入口把app实例挂到router.app上调用$watch必然失效。
你目前在组件内把当前组件实例赋值给router.app的方案存在明确缺陷:如果调用插件的组件被卸载,对应的watcher会被自动回收导致监听中断;如果多个组件重复调用插件,还会覆盖router.app的引用,造成之前的监听上下文错乱。另外router.app不是vue-router的公开API属性,后续版本迭代如果内部占用了同名属性,会直接导致逻辑崩溃。
方案1:脱离组件实例直接监听路由(最稳妥)
Vue3 的响应式API可以完全脱离组件上下文独立运行,不需要依附任何组件或应用实例,这是Vue3官方推荐的跨实例/外部逻辑监听方案。
对应Vue3的vue-router 4版本中,router实例自带的currentRoute是全局唯一的响应式ref对象,和组件内注入的$route值完全同步,直接引入Vue的watch方法监听对应query字段即可,不需要对原有调用逻辑做侵入式修改:
import { watch } from 'vue' function myPluginName(router) { watch( () => router.currentRoute.value.query[this.config.name], (newVal, oldVal) => { // 原有业务逻辑,完全在Vue实例外部运行 } ) }
这个方案的优势:
- 不需要在入口文件手动给router挂载app属性
- 不需要在业务组件内传入/赋值组件实例
- 监听生命周期和router实例完全绑定,不会随任意业务组件卸载中断
- 多次调用插件会生成独立的watcher,不会互相覆盖
- 原有组件内的调用方式
myPluginName(this.$router)完全不需要修改,兼容旧调用逻辑
方案2:依附根组件实例监听
如果你的业务逻辑需要和根组件生命周期绑定(比如根组件卸载时自动停止监听),可以在应用挂载完成后拿到根组件实例传入插件,不要在业务组件内随意修改router属性:
入口文件调整为:
const app = createApp(App) app.use(router) // 注意:必须在mount执行后才能拿到根组件实例,mount前实例未创建 app.mount('#app') const rootComponentInstance = app._instance.proxy // 初始化插件时传入根实例 myPluginName(router, rootComponentInstance)
插件逻辑对应调整:
function myPluginName(router, instance) { instance.$watch( () => router.currentRoute.value.query[this.config.name], () => { // 业务逻辑 } ) }
注意:非必要不要修改第三方库实例上的自定义属性,尤其是没有写在官方文档里的内部属性,这类属性不会做跨版本兼容承诺,版本升级时很容易出现不可预期的问题。
内容的提问来源于stack exchange,提问作者CaptainCarl

