Vue2 和 Vue3 中祖先组件和子孙组件的通信方法和区别

在 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

最佳实践建议

  1. 优先使用 Props 和 Emit:适用于直接父子组件通信
  2. 跨层级通信首选 Provide/Inject:比事件总线更容易维护
  3. 复杂状态管理使用 Vuex/Pinia:适用于大型应用的状态管理
  4. 避免过度使用 Refs:破坏组件封装性
  5. 合理使用事件总线:适用于简单的兄弟组件通信
相关推荐
勇气要爆发1 小时前
问:当服务器资源有限,前端项目高并发优化策略
前端·性能优化
鹏多多1 小时前
前端组件二次封装实战:Vue+React基于Element UI/AntD的高效封装策略
前端·vue.js·react.js
桧***攮1 小时前
前端在移动端中的性能优化
前端·性能优化
国服第二切图仔1 小时前
Electron for 鸿蒙PC项目实战案例之简单统计组件
javascript·electron·harmonyos
小小码农一只1 小时前
Spring WebFlux与响应式编程:构建高效的异步Web应用
java·前端·spring·spring webflux
北极糊的狐1 小时前
使用 vue-awesome-swiper 实现轮播图(Vue3实现教程)
前端·javascript·vue.js
W.Y.B.G1 小时前
vue3项目中集成高德地图使用示例
前端·javascript·网络
王兆龙1681 小时前
简易版增删改查
前端·vscode·vue
Jonathan Star1 小时前
`npx prettier --write . --end-of-line lf` 是一条用于**格式化代码**的命令
前端·css3