Vue2与Vue3通信机制对比
Vue 3 在通信机制上进行了重大改进,移除了部分 Vue 2 的 API,引入了 Composition API 的函数式通信方式。通过详细的对比表格,清晰展示 Vue 2 和 Vue 3 在各种通信机制上的具体差异,有助于快速掌握迁移要点。
一、通信机制概览
Vue 的组件通信主要分为以下几类:
- 事件通信:$emit/$on/$off、事件总线、自定义事件
- 父子组件通信:props/emit、$attrs/$listeners、$refs
- 跨层级通信:provide/inject、$parent/$children
二、核心差异概览
Vue 3 通信机制的主要变化:
|
变化类型 |
具体内容 |
影响范围 |
|
API 移除 |
$on/$off/$once 完全移除 |
事件总线模式 |
|
API 移除 |
$children 移除 |
子组件批量访问 |
|
API 移除 |
$listeners 独立存在移除 |
属性和事件透传 |
|
API 合并 |
$listeners 合并到 $attrs |
组件透传简化 |
|
使用调整 |
emit 需先定义再使用 |
Composition API |
|
函数化改造 |
provide/inject 变为函数 |
跨层级通信 |
|
函数化改造 |
ref() 替代 this.$refs |
模板 ref |
|
语法统一 |
v-model:xxx 替代 .sync |
双向绑定 |
三、详细对比
以下表格详细列出了事件通信和组件通信的所有核心差异,包括特性说明、具体实现方式和完整代码示例。
|
内容特性 |
Vue 2 特性 |
Vue 2 案例 |
Vue 3 特性 |
Vue 3 案例 |
|
事件监听 |
Vue 实例自带事件监听方法 |
// 全局 EventBus |
移除 $on/$off/$once |
// utils/eventBus.js |
|
事件取消 |
Vue 实例自带事件取消方法 |
// 清理事件监听 |
移除 $off |
// 清理事件监听 |
|
单次监听 |
Vue 实例自带单次监听方法 |
this.$bus.$once('init', (data) => { |
移除 $once |
const handler = (data) => { |
|
触发事件 |
Options API 中直接调用 |
export default { |
Composition API 需先定义 |
<script setup> |
|
事件总线 |
使用 Vue 实例作为事件总线 |
// main.js |
移除 Vue 实例事件方法 |
// eventBus.js |
|
父子通信 |
使用 props 选项 |
export default { |
使用 defineProps 函数 |
<script setup> |
|
父子通信 |
直接调用 this.$emit() |
export default { |
必须先声明事件 |
<script setup> |
|
属性透传 |
$attrs 和 $listeners 分离 |
<template> |
$listeners 合并到 $attrs |
<template> |
|
跨层级通信 |
provide 选项或函数 |
export default { |
provide() 函数 |
<script setup> |
|
跨层级通信 |
inject 选项 |
export default { |
inject() 函数 |
<script setup> |
|
子组件访问 |
this.$children 数组 |
export default { |
移除 $children |
<script setup> |
|
模板引用 |
字符串 ref |
<template> |
ref() 响应式变量 |
<script setup> |
|
双向绑定 |
.sync 修饰符 |
<template> |
v-model:xxx 统一语法 |
<template> |
|
默认双向绑定 |
默认 prop 为 value |
<template> |
默认 prop 为 modelValue |
<template> |
|
TypeScript 支持 |
类型定义较弱 |
export default { |
完善的类型推断 |
<script setup lang="ts"> |
|
响应式处理 |
默认非响应式 |
export default { |
天然响应式 |
<script setup> |
|
组件方法暴露 |
可通过 $children 或 $refs |
export default { |
Composition API 默认封闭 |
<script setup> |
四、实际应用示例
以下通过完整示例展示 Vue 2 和 Vue 3 中各种通信方式的实现:
示例1:父子组件通信(props + emit)
Vue 2 Options API:
<!-- Parent.vue -->
<template>
<Child :value="count" @update="handleUpdate" />
</template>
<script>
export default {
data() {
return { count: 0 }
},
methods: {
handleUpdate(val) {
this.count = val
}
}
}
</script>
<!-- Child.vue -->
<script>
export default {
props: ['value'],
methods: {
increment() {
this.$emit('update', this.value + 1)
}
}
}
</script>
Vue 3 Composition API:
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const count = ref(0)
const handleUpdate = (val) => {
count.value = val
}
</script>
<template>
<Child :value="count" @update="handleUpdate" />
</template>
<!-- Child.vue -->
<script setup>
const props = defineProps(['value'])
const emit = defineEmits(['update'])
const increment = () => {
emit('update', props.value + 1)
}
</script>
示例2:跨层级通信
Vue 2:
export default {
provide() {
return {
theme: this.theme
}
},
inject: ['theme']
}
Vue 3:
<script setup>
import { ref, provide, inject } from 'vue'
// 父组件提供数据
const theme = ref('dark')
provide('theme', theme)
// 子组件注入数据
const currentTheme = inject('theme')
</script>
示例3:事件总线替代方案
Vue 2(EventBus):
// main.js
Vue.prototype.$bus = new Vue()
// Component A
this.$bus.$emit('user-login', userData)
// Component B
this.$bus.$on('user-login', (data) => {
console.log(data)
})
Vue 3(mitt库):
// eventBus.js
import mitt from 'mitt'
const emitter = mitt()
export default emitter
// Component A
import emitter from './eventBus'
emitter.emit('user-login', userData)
// Component B
import emitter from './eventBus'
emitter.on('user-login', (data) => {
console.log(data)
})
示例4:模板 ref 通信
Vue 2:
<template>
<input ref="inputRef" />
</template>
<script>
export default {
mounted() {
this.$refs.inputRef.focus()
}
}
</script>
Vue 3:
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
</script>
<template>
<input ref="inputRef" />
</template>
五、最佳实践建议
在实际开发中,合理选择通信方式能提升代码质量和可维护性:
1、父子组件通信优先级:
- 简单数据传递:使用 props + emit(最标准)
- 复杂表单/组件:使用 v-model:xxx(Vue 3 推荐)
- 深层属性透传:使用 $attrs + inheritAttrs: false
2、跨层级通信选择:
- 3层以内:优先 props/emit 逐层传递(数据流清晰)
- 深层嵌套:使用 provide/inject(避免 prop drilling)
- 兄弟组件:优先状态管理,必要时使用事件库
3、Composition API 通信优势:
- defineProps/defineEmits 自动类型推断
- provide/inject 支持响应式数据
- 逻辑复用更简单(Composables)
4、避免通信陷阱:
- 不要过度使用 provide/inject(数据流不透明)
- 不要滥用 ref 直接操作子组件(耦合度高)
- 移除 this.$children 后,优先响应式方案
5、事件总线替代建议:
- Vue 3 移除了 $on/$off,推荐 mitt 库(轻量)
- 事件名建议常量定义,避免冲突
- 记得在 onUnmounted 中 off 清理监听
6、TypeScript 支持:
- Vue 3 的 defineProps/defineEmits 可定义类型
- provide/inject 支持 InjectionKey 类型安全
- 事件总线建议使用泛型定义事件类型
7、响应式通信注意:
- provide 的数据建议用 ref/reactive 包装
- inject 接收时保持响应式,避免解构丢失
- 可配合 readonly 实现单向数据流
总结
Vue 3 在通信机制上的改进体现了框架演进的方向:
1. 从实例方法到函数式 API - 更灵活、可复用、类型安全
2. 从分离 API 到统一 API - 更简洁、一致($listeners 合并到 $attrs)
3. 从隐式访问到显式声明 - 更清晰、规范(emit 需定义,defineExpose 需暴露)
4. 从手动响应式到天然响应式 - provide/inject 自动响应式传递
浙公网安备 33010602011771号