返回首页

Vue组件通信

布莱克2026-05-10 15:49已编辑
Tip:文章封面与内容无关,作者旅游时拍摄,因为没什么值得把四季都错过!

Vue2组件通信方式:

Props / $emit (父子组件通信)

// 父组件
<template>
  <Child :message="parentMsg" @childEvent="handleEvent" />
</template>

<script>
export default {
  data() {
    return { parentMsg: 'Hello from parent' }
  },
  methods: {
    handleEvent(data) {
      console.log('收到子组件事件:', data)
    }
  }
}
</script>

// 子组件
<script>
export default {
  props: ['message'],
  methods: {
    sendToParent() {
      this.$emit('childEvent', '数据来自子组件')
    }
  }
}
</script>

provide / inject (跨层级传递)

//provide/inject 默认不是响应式的,需要传递响应式对象才能实现响应式。
// 祖先组件
export default {
  provide() {
    return {
      userInfo: this.userInfo,
      updateUser: this.updateUser
    }
  },
  data() {
    return { userInfo: { name: '张三' } }
  },
  methods: {
    updateUser(name) {
      this.userInfo.name = name
    }
  }
}

// 后代组件
export default {
  inject: ['userInfo', 'updateUser'],
  mounted() {
    console.log(this.userInfo)
    this.updateUser('李四')
  }
}

Event Bus (事件总线)

// EventBus 对象本质上就是一个普通的 Vue 实例,没有 template等等,但拥有 Vue 实例的所有能力
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()

// 组件 A(发送事件)
import { EventBus } from './event-bus'
EventBus.$emit('custom-event', { data: 'some data' })

// 组件 B(监听事件)
import { EventBus } from './event-bus'
EventBus.$on('custom-event', (payload) => {
  console.log(payload)
})

// 监听事件组件销毁前移除监听
beforeDestroy() {
  EventBus.$off('custom-event')
}

为什么 EventBus 能跨组件通信?

因为 A 和 B 导入的是同一个对象(单例模式)
所以它们操作的是同一个事件中心

EventBus 本质上是一个全局的发布-订阅对象,它会持久存在(除非页面刷新或关闭)。当一个组件监听了一个事件后,EventBus 内部会持有该监听函数的引用。

  • 发送事件的组件:只是调用 $emit 触发事件,EventBus 不持有该组件的任何引用,组件销毁不影响 EventBus
  • 监听事件的组件:通过 $on 将回调函数注册到 EventBus 上,EventBus 持有这个回调函数的引用,如果回调函数中访问了组件的数据,就会形成引用链

如果不移除会有什么后果?

内存泄漏

当组件 B 被销毁(如路由切换、v-if 隐藏)后:

  • 组件 B 虽然从 DOM 中移除了
  • 但 EventBus 仍然持有回调函数的引用
  • 回调函数通过闭包持有组件 B 的 this 引用
  • 组件 B 及其所有数据(包括 hugeData)永远不会被垃圾回收

重复监听导致逻辑错误

//用户反复进入/离开组件多次
// 组件
mounted() {
  EventBus.$on('update-count', () => {
    this.count++  // 每次进入组件都会执行多次
  })
}

// 场景:用户进入页面 → 离开 → 重新进入 → 点击按钮触发事件
// 点击一次,count 会增加 3 次(因为注册了 3 个监听器)

组件销毁后仍然执行逻辑


Vuex (全局状态管理)

// store.js
export default new Vuex.Store({
  state: { count: 0 },
  mutations: { increment(state) { state.count++ } },
  actions: { incrementAsync({ commit }) { setTimeout(() => commit('increment'), 1000) } },
  getters: { doubleCount: state => state.count * 2 }
})

// 组件中使用
this.$store.commit('increment')
this.$store.dispatch('incrementAsync')
this.$store.getters.doubleCount


Vue3组件通信:

Props / $emit (变化较小)

<!-- 子组件 Child.vue -->
<script setup>
// 定义 props
const props = defineProps({
  message: String,
  count: Number
})

// 定义 emits
const emit = defineEmits(['update', 'childEvent'])

const sendToParent = () => {
  emit('childEvent', '数据来自子组件')
}
</script>

<!-- 父组件 -->
<template>
  <Child :message="msg" @childEvent="handleEvent" />
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const msg = ref('Hello from parent')
const handleEvent = (data) => {
  console.log(data)
}
</script>


使用 mitt 实现 Event Bus

Vue 3 专注于组件核心功能,EventBus 属于“模式”而非“核心功能”

// event-bus.js
import mitt from 'mitt'
export const emitter = mitt()

// 组件 A(发送事件)
import { emitter } from './event-bus'
emitter.emit('custom-event', { data: 'some data' })

// 组件 B(监听事件)
import { emitter } from './event-bus'
import { onUnmounted } from 'vue'

const handler = (payload) => {
  console.log(payload)
}

emitter.on('custom-event', handler)

// 组件卸载时移除监听
onUnmounted(() => {
  emitter.off('custom-event', handler)
})


Vuex 4 / Pinia (状态管理)

// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '张三',
    age: 18
  }),
  getters: {
    adult: (state) => state.age >= 18
  },
  actions: {
    updateName(name) {
      this.name = name
    }
  }
})

// 组件中使用
<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// 使用 storeToRefs 保持响应性
const { name, age, adult } = storeToRefs(userStore)
const { updateName } = userStore

// 直接调用 action
updateName('李四')
</script>


assistant