迁移Vue2项目至Vue3时this.$root.$on的替代方案是什么?
Vue3 已经彻底移除了实例上自带的$on、$off、$once三个事件API,不再支持直接通过组件实例(包括$root根实例)绑定自定义事件做跨组件通信,所以TS会直接抛出Property '$on' does not exist on type 'ComponentPublicInstance'的类型错误。
你原有逻辑是在根实例上绑定openLotsenFinder全局事件,触发时移除window上的keyup监听,选下面任意一种方案替换即可,优先选第一种,改造成本最低。
方案1:独立事件总线(适配原有逻辑,改造成本最低)
不需要引入额外第三方依赖,先单独实现一个极简的事件总线工具:
// 存为 src/utils/eventBus.js const createEventBus = () => { const eventMap = new Map() return { on(event, cb) { if (!eventMap.has(event)) eventMap.set(event, []) eventMap.get(event).push(cb) }, off(event, cb) { if (!eventMap.has(event)) return if (!cb) return eventMap.delete(event) const cbList = eventMap.get(event) const idx = cbList.indexOf(cb) if (idx > -1) cbList.splice(idx, 1) }, emit(event, ...payload) { if (!eventMap.has(event)) return eventMap.get(event).forEach(cb => cb(...payload)) } } } export default createEventBus()
然后把原来的$root.$on逻辑替换为引用这个事件总线即可,注意一定要在组件卸载时主动解绑事件,避免内存泄漏:
import eventBus from '@/utils/eventBus' export default { data() { return { eventsOnLotsenFinder: false } }, mounted() { if (!this.eventsOnLotsenFinder) { // 替换原有 this.$root.$on 逻辑 eventBus.on('openLotsenFinder', this.handleLotsenFinderOpen) this.eventsOnLotsenFinder = true } }, // Vue3 卸载生命周期钩子改名了,对应Vue2的beforeDestroy beforeUnmount() { eventBus.off('openLotsenFinder', this.handleLotsenFinderOpen) }, methods: { handleLotsenFinderOpen() { window.removeEventListener('keyup', this.onWindowKeyUp) }, onWindowKeyUp() { // 原有keyup监听逻辑保持不变 } } }
项目里所有原来触发this.$root.$emit('openLotsenFinder')的位置,统一替换为eventBus.emit('openLotsenFinder')就能跑通原有逻辑。
小提示:原来的写法直接把匿名箭头函数传进
$on,后续没法精准定位回调做解绑,很容易造成内存泄漏,改造的时候最好把回调抽成组件方法,方便绑定和解绑。
方案2:全局状态驱动(适合关联全局业务的场景)
如果openLotsenFinder的触发和全局业务状态强关联,可以直接用Pinia管理对应状态,在当前组件watch这个状态的变化,状态变更时执行移除keyup监听的逻辑即可,不需要走事件通信。
方案3:provide/inject 跨层级通信
如果触发事件的组件和当前组件是固定的祖先/后代关系,可以在根组件通过provide暴露触发回调,后代组件通过inject拿到回调后触发,这种方式适合层级固定的场景,灵活度不如独立事件总线。
注意:不要尝试手动给Vue3组件实例挂载$on方法做兼容,后续版本迭代会有隐性bug,TS类型支持也很差。
内容的提问来源于stack exchange,提问作者Pazulay

