药师学社
65.31M · 2026-02-04
在 Vue 开发中,组件通信是构建复杂应用的基础。随着 Vue 3 的普及,通信方式发生了不少变化(如 defineProps 的引入、EventBus 的退场)。本文将对比 Vue 2 与 Vue 3,带你梳理最常用的 5 种通信方案。
这是最常用的通信方式,遵循“Props 向下传递,Emit 向上通知”的原则。
props 选项。this.$emit。在 Vue 3 <script setup> 中,我们使用 defineProps 和 defineEmits。
父组件:Parent.vue
<template>
<ChildComponent :id="currentId" @childEvent="handleChild" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ChildComponent from './Child.vue';
const currentId = ref<string>('001');
const handleChild = (msg: string) => {
console.log('接收到子组件消息:', msg);
};
</script>
子组件:Child.vue
<script setup lang="ts">
// 使用 TS 类型定义 Props
const props = defineProps<{
id: string
}>();
// 使用 TS 定义 Emits,具备更好的类型检查
const emit = defineEmits<{
(e: 'childEvent', args: string): void;
}>();
const sendMessage = () => {
emit('childEvent', '这是来自子组件的参数');
};
</script>
有时父组件需要直接调用子组件的内部方法。
直接通过 this.$refs.childRef.someMethod() 调用。
Vue 3 的组件默认是关闭的。如果父组件想访问子组件的方法,子组件必须使用 defineExpose。
子组件:Child.vue
<script setup lang="ts">
const childFunc = () => {
console.log('子组件方法被调用');
};
// 必须手动暴露,父组件才能访问
defineExpose({
childFunc
});
</script>
父组件:Parent.vue
<template>
<Child ref="childRef" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Child from './Child.vue';
// 这里的类型定义有助于获得代码提示
const childRef = ref<InstanceType<typeof Child> | null>(null);
onMounted(() => {
childRef.value?.childFunc();
});
</script>
利用一个新的 Vue 实例作为中央调度器。
import Vue from 'vue';
export const EventBus = new Vue();
// 组件 A 发送
EventBus.$emit('event', data);
// 组件 B 接收
EventBus.$on('event', (data) => { ... });
Vue 3 官方已移除了 $on、$off 和 $once 方法,因此不再支持直接通过 Vue 实例创建 EventBus。
mitt 或 tiny-emitter。provide / inject 实现跨级通信。provide / inject 示例:
App.vue)<template>
<div class="ancestor">
<h1>祖先组件</h1>
<p>当前主题:{{ theme }}</p>
<Middle />
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue';
import Middle from './Middle.vue';
// 1. 定义响应式数据
const theme = ref<'light' | 'dark'>('light');
// 2. 定义修改数据的方法(推荐在提供者内部定义,保证数据流向清晰)
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
};
// 3. 注入 key 和对应的值/方法
provide('theme', theme);
provide('toggleTheme', toggleTheme);
</script>
中间组件:无需操作 (Middle.vue)
中间组件不需要显式接收 theme,直接透传即可
后代组件:注入并使用 (DeepChild.vue)
<template>
<div class="descendant">
<h3>深层子组件</h3>
<p>接收到的主题:{{ theme }}</p>
<button @click="toggleTheme">切换主题</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue';
// 使用 inject 获取,第二个参数为默认值(可选)
const theme = inject('theme');
const toggleTheme = inject<() => void>('toggleTheme');
</script>
当应用变得庞大,组件间的关系交织成网时,我们需要一个“单一事实来源”。
Vuex:Vue 2 时代的标准。基于 Mutation(同步)和 Action(异步)。
Pinia:Vue 3 的官方推荐。
state、getters、actions。Pinia 示例:
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
name: '张三',
age: 18
}),
actions: {
updateName(newName: string) {
this.name = newName;
}
}
});
defineExpose 时,尽量只暴露必要的接口,遵循最小暴露原则。new Vue() 来做事件总线,应当转向 Pinia 或全局状态。