在 Vue 应用开发中,组件间通信是非常重要的概念,特别是祖先组件和子孙组件之间的通信。本文将详细介绍 Vue2 和 Vue3 中各种通信方法及其区别。
1. Props / Events(最基础的父子通信)
Vue2 中的实现
javascript
// 父组件
<template>
<child-component :message="parentMessage" @child-event="handleChildEvent" />
</template>
<script>
export default {
data() {
return {
parentMessage: 'Hello from parent'
}
},
methods: {
handleChildEvent(data) {
console.log('Received from child:', data)
}
}
}
</script>
// 子组件
<template>
<div>
<p>{{ message }}</p>
<button @click="sendToParent">Send to parent</button>
</div>
</template>
<script>
export default {
props: ['message'],
methods: {
sendToParent() {
this.$emit('child-event', 'Hello from child')
}
}
}
</script>
Vue3 中的实现
javascript
// 父组件
<template>
<child-component :message="parentMessage" @child-event="handleChildEvent" />
</template>
<script setup>
import { ref } from 'vue'
const parentMessage = ref('Hello from parent')
const handleChildEvent = (data) => {
console.log('Received from child:', data)
}
</script>
// 子组件
<template>
<div>
<p>{{ message }}</p>
<button @click="sendToParent">Send to parent</button>
</div>
</template>
<script setup>
defineProps(['message'])
const emit = defineEmits(['child-event'])
const sendToParent = () => {
emit('child-event', 'Hello from child')
}
</script>
2. Provide / Inject(跨层级组件通信)
Vue2 中的实现
javascript
// 祖先组件
export default {
provide() {
return {
theme: 'dark',
user: this.currentUser
}
},
data() {
return {
currentUser: { name: 'John', role: 'admin' }
}
}
}
// 子孙组件
export default {
inject: ['theme', 'user'],
mounted() {
console.log(this.theme) // 'dark'
console.log(this.user) // { name: 'John', role: 'admin' }
}
}
Vue3 中的实现
javascript
// 祖先组件 (Options API)
export default {
provide() {
return {
theme: 'dark',
user: this.currentUser
}
},
data() {
return {
currentUser: { name: 'John', role: 'admin' }
}
}
}
// 祖先组件 (Composition API)
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
const currentUser = ref({ name: 'John', role: 'admin' })
provide('theme', theme)
provide('user', currentUser)
</script>
// 子孙组件 (Options API)
export default {
inject: ['theme', 'user']
}
// 子孙组件 (Composition API)
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
const user = inject('user')
</script>
3. $attrs 和 listeners(属性透传)
Vue2 中的实现
javascript
// 祖先组件
<template>
<middle-component class="wrapper" @custom-click="handleClick" />
</template>
// 中间组件
<template>
<child-component v-bind="$attrs" v-on="$listeners" />
</template>
export default {
inheritAttrs: false
}
// 最终子组件
<template>
<button @click="$emit('custom-click')">Click me</button>
</template>
Vue3 中的实现
javascript
// Vue3 中 $listeners 被移除,事件监听器包含在 $attrs 中
// 中间组件
<template>
<child-component v-bind="$attrs" />
</template>
export default {
inheritAttrs: false
}
// 或者使用 Composition API
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
4. Refs 访问子组件实例
Vue2 中的实现
javascript
// 父组件
<template>
<child-component ref="childRef" />
</template>
export default {
methods: {
callChildMethod() {
this.$refs.childRef.childMethod()
}
}
}
// 子组件
export default {
methods: {
childMethod() {
console.log('Child method called')
}
}
}
Vue3 中的实现
javascript
// 父组件
<template>
<child-component ref="childRef" />
</template>
<script setup>
import { ref } from 'vue'
const childRef = ref(null)
const callChildMethod = () => {
childRef.value.childMethod()
}
</script>
// 子组件
<script setup>
const childMethod = () => {
console.log('Child method called')
}
defineExpose({
childMethod
})
</script>
5. Event Bus(事件总线)
Vue2 中的实现
javascript
// 创建事件总线
// eventBus.js
import Vue from 'vue'
export default new Vue()
// 组件 A
import eventBus from './eventBus.js'
export default {
methods: {
sendMessage() {
eventBus.$emit('message', 'Hello from component A')
}
},
mounted() {
eventBus.$on('reply', (data) => {
console.log(data)
})
},
beforeDestroy() {
eventBus.$off('reply')
}
}
// 组件 B
import eventBus from './eventBus.js'
export default {
methods: {
replyMessage() {
eventBus.$emit('reply', 'Hello from component B')
}
},
mounted() {
eventBus.$on('message', (data) => {
console.log(data)
})
},
beforeDestroy() {
eventBus.$off('message')
}
}
Vue3 中的实现
javascript
// Vue3 中不再支持直接创建 Vue 实例作为事件总线
// 需要使用第三方库或者 mitt
// 使用 mitt
// npm install mitt
// eventBus.js
import mitt from 'mitt'
export default mitt()
// 组件中使用方式与 Vue2 类似
import eventBus from './eventBus.js'
// 发送事件
eventBus.emit('message', 'Hello')
// 监听事件
eventBus.on('message', data => {
console.log(data)
})
// 移除事件监听
eventBus.off('message')
6. Vuex/Pinia 状态管理
Vue2 + Vuex
javascript
// store.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: null,
theme: 'light'
},
mutations: {
setUser(state, user) {
state.user = user
},
setTheme(state, theme) {
state.theme = theme
}
}
})
// 组件中使用
export default {
computed: {
user() {
return this.$store.state.user
}
},
methods: {
updateUser(user) {
this.$store.commit('setUser', user)
}
}
}
Vue3 + Pinia
javascript
// store/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
theme: 'light'
}),
actions: {
setUser(user) {
this.user = user
},
setTheme(theme) {
this.theme = theme
}
}
})
// 组件中使用
<script setup>
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
// 访问状态
console.log(userStore.user)
// 调用 action
userStore.setUser({ name: 'John' })
</script>
主要区别总结
| 特性 | Vue2 | Vue3 |
|---|---|---|
| Provide/Inject | 基本相同 | 支持 Composition API |
| attrs 和 listeners | 分离的属性和事件 | 事件包含在 $attrs 中 |
| Refs | 直接访问子组件 | 需要 defineExpose 暴露 |
| Event Bus | 原生支持 | 需要第三方库 |
| 生命周期 | beforeDestroy | beforeUnmount |
| 响应式系统 | Object.defineProperty | Proxy |
最佳实践建议
- 优先使用 Props 和 Emit:适用于直接父子组件通信
- 跨层级通信首选 Provide/Inject:比事件总线更容易维护
- 复杂状态管理使用 Vuex/Pinia:适用于大型应用的状态管理
- 避免过度使用 Refs:破坏组件封装性
- 合理使用事件总线:适用于简单的兄弟组件通信