// 父组件
<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 默认不是响应式的,需要传递响应式对象才能实现响应式。
// 祖先组件
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('李四')
}
}// 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')
}因为 A 和 B 导入的是同一个对象(单例模式)
所以它们操作的是同一个事件中心EventBus 本质上是一个全局的发布-订阅对象,它会持久存在(除非页面刷新或关闭)。当一个组件监听了一个事件后,EventBus 内部会持有该监听函数的引用。
内存泄漏
当组件 B 被销毁(如路由切换、v-if 隐藏)后:
//用户反复进入/离开组件多次
// 组件
mounted() {
EventBus.$on('update-count', () => {
this.count++ // 每次进入组件都会执行多次
})
}
// 场景:用户进入页面 → 离开 → 重新进入 → 点击按钮触发事件
// 点击一次,count 会增加 3 次(因为注册了 3 个监听器)// 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<!-- 子组件 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>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)
})// 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>