
1.vue3为什么去掉了onoff?
1.设计理念的调整
Vue 3 更加注重组件间通信的明确性和可维护性。$on
这类事件 API 本质上是一种 "发布 - 订阅" 模式,容易导致组件间关系模糊(多个组件可能监听同一个事件,难以追踪事件来源和流向)。Vue 3 推荐使用更明确的通信方式,如: - 父子组件通过 props
和 emit
通信 - 跨组件通信使用 provide/inject
或 Pinia/Vuex 等状态管理库 - 复杂场景可使用专门的事件总线库(如 mitt
)
2.与 Composition API 的适配
Vue 3 主推的 Composition API 强调逻辑的封装和复用,而 $on
基于选项式 API 的实例方法,与 Composition API 的函数式思维不太契合。移除后,开发者可以更自然地使用响应式变量或第三方事件库来实现类似功能。
3.减少潜在问题
$on
容易导致内存泄漏(忘记解绑事件)- 事件名称可能冲突(全局事件总线尤其明显)
- 不利于 TypeScript 类型推断,难以实现类型安全
vue3中如何使用事件总线实现跨级组件之间的通信?
1.可以通过第三方库(如 mitt
或 tiny-emitter
)替代,示例如下:
js
// 安装 mitt:npm install mitt
import mitt from 'mitt'
// 创建事件总线实例
const emitter = mitt()
// 监听事件
emitter.on('event-name', (data) => {
console.log('收到事件:', data)
})
// 触发事件
emitter.emit('event-name', { message: 'hello' })
// 移除事件监听
emitter.off('event-name', handler)
2.使用 Vue3 提供的 provide/inject
js
// 父组件提供事件总线
import { provide, ref } from 'vue'
export default {
setup() {
const events = ref({})
const on = (name, callback) => {
events.value[name] = callback
}
const emit = (name, data) => {
if (events.value[name]) {
events.value[name](data)
}
}
provide('eventBus', { on, emit })
}
}
// 子组件使用
import { inject } from 'vue'
export default {
setup() {
const eventBus = inject('eventBus')
// 监听事件
eventBus.on('event-name', (data) => {
// 处理逻辑
})
// 发送事件
eventBus.emit('event-name', data)
}
}
3.利用 Vue 实例的自定义事件 虽然 Vue3 移除了 $on
、$off
等方法,但可以创建一个空的 Vue 实例作为事件总线,利用其自定义事件 API:
js
// eventBus.js
import { createApp } from 'vue'
const app = createApp({})
export default app
// 发送事件
import bus from './eventBus'
bus.config.globalProperties.$emit('event-name', data)
// 监听事件(在组件中)
import { getCurrentInstance } from 'vue'
export default {
mounted() {
const instance = getCurrentInstance()
instance.appContext.config.globalProperties.$on('event-name', (data) => {
// 处理逻辑
})
}
}
4.使用 reactive 创建事件总线
js
// 组件A中发送事件
import eventBus from './eventBus'
eventBus.emit('user-updated', { name: '张三' })
// 组件B中监听事件
import eventBus from './eventBus'
export default {
mounted() {
this.handleUserUpdate = (user) => {
console.log('用户更新了', user)
}
eventBus.on('user-updated', this.handleUserUpdate)
},
beforeUnmount() {
// 组件卸载时移除监听,避免内存泄漏
- 使用 Pinia/Vuex 状态管理
对于复杂应用,更推荐使用状态管理库来处理组件间通信,通过修改共享状态来实现组件间的数据传递。
总结
- 在 Vue3 中实现事件总线,最推荐的方式是使用
mitt
库,它轻量高效且 API 简洁,能够很好地替代 Vue2 中的事件总线功能。对于简单场景也可以使用provide/inject
方案,但对于大型应用,状态管理库会是更优选.择。- 手动实现的事件总线需要注意在组件卸载时移除事件监听,避免内存泄漏; 注意考虑 "同一事件绑定多个回调" 的去重逻辑;避免没有事件触发时的异常捕获,单个回调报错可能阻断整个事件流程。
2.vue3中的defineEmits
$emit又是什么关系?
Vue3 并没有完全移除 $emit
(它仍然用于子组件向父组件传递事件)。
defineEmits
是 Vue3 提供的编译时宏命令 (Compiler Macro),用于在 <script setup>
语法中声明组件可以触发的事件,主要作用是:
- 明确组件对外暴露的事件,提升代码可读性和可维护性
- 提供 TypeScript 类型校验(如果使用 TS)
- 在开发环境下对未声明的事件触发给出警告
使用方式(在 <script setup>
中)
vue
<template>
<button @click="handleClick">点击触发事件</button>
</template>
<script setup>
// 声明组件可以触发的事件
const emit = defineEmits(['change', 'update'])
const handleClick = () => {
// 触发事件并传递数据
emit('change', 'hello')
emit('update', { id: 1, name: 'test' })
}
</script>
带类型校验的用法(TypeScript)
vue
<script setup lang="ts">
// 用类型标注事件名称和参数类型
const emit = defineEmits<{
(e: 'change', value: string): void
(e: 'update', data: { id: number; name: string }): void
}>()
// 错误示例:参数类型不匹配会报错
emit('change', 123) // TS 类型错误
</script>
注意点
-
仅在
<script setup>
中可用 :defineEmits
是编译时宏,不需要导入,直接使用(Vue 编译器会处理) -
替代 Vue2 的
emits
选项 :在非<script setup>
语法中,仍可以用emits
选项声明事件:javascriptexport default { emits: ['change', 'update'], // 等价于 defineEmits setup(props, { emit }) { // ... } }
-
与
$emit
的关系 :defineEmits
返回的emit
函数与this.$emit
功能一致,但在<script setup>
中推荐使用前者(更符合组合式 API 风格)