在Vue开发中,组件是页面的独立组成单元,而组件通信是实现组件协作的核心------小到父子组件的简单数据传递,大到跨页面组件的状态共享,选对通信方式直接决定代码的可维护性与性能。本文将系统梳理Vue中所有组件通信方式,涵盖完整语法、场景适配与选型指南,帮你搞定组件间的"消息传递"。
一、父子组件通信:基础协作方式
父子组件是最常见的组件关系,通信以"单向数据流"为核心,同时提供双向同步的语法糖简化开发。
1. props + $emit:父子通信的官方基础
props是父→子的数据传递通道 ,$emit是子→父的事件反馈通道,二者结合实现父子组件的双向交互。
(1)props:父→子的带校验数据传递
props支持类型校验、必填校验、默认值、自定义校验,确保数据合法性,完整语法如下:
javascript
// 子组件中定义props
props: {
// 基础类型+必填+默认值(必传项无需default)
msg: {
type: String, // 支持类型:String/Number/Boolean/Array/Object/Function等
required: true,
default: '默认文本' // 非必传项的默认值(对象/数组需用函数返回)
},
// 自定义校验规则
count: {
type: Number,
validator: (value) => {
// 校验逻辑:返回true则通过,false则控制台报错
return value > 0;
}
}
}
注意 :props是单向数据流 ,子组件不能直接修改props(会触发警告),需通过$emit通知父组件修改。
(2)$emit:子→父的事件反馈
子组件通过$emit(事件名, 传递数据)触发自定义事件,父组件通过@事件名监听并接收数据,示例如下:
vue
<!-- 父组件 -->
<template>
<Child :msg="parentMsg" :count="parentCount" @updateCount="handleUpdate" />
</template>
<script>
import Child from './Child.vue';
export default {
components: { Child },
data() { return { parentMsg: 'Hello', parentCount: 1 } },
methods: {
handleUpdate(newCount) { this.parentCount = newCount }
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<p>父传子:{{ msg }}(数量:{{ count }})</p>
<button @click="$emit('updateCount', count + 1)">数量+1</button>
</div>
</template>
<script>
export default {
props: { msg: { type: String, required: true }, count: { type: Number, validator: v => v > 0 } }
}
</script>
2. v-model:表单组件的双向绑定语法糖
v-model是props: value + $emit('input')的语法糖,专门简化表单组件 的双向同步,默认绑定value属性与input事件。
(1)基础用法
vue
<!-- 父组件 -->
<template>
<CustomInput v-model="username" />
</template>
<script>
export default { data() { return { username: '' } } }
</script>
<!-- 子组件 -->
<template>
<input :value="value" @input="$emit('input', $event.target.value)" />
</template>
<script>
export default { props: { value: String } }
</script>
(2)自定义v-model(适配非input组件)
对于开关、单选框等组件,可通过model选项自定义绑定的prop与事件:
javascript
// 子组件
export default {
model: {
prop: 'checked', // 自定义v-model对应的prop名
event: 'change' // 自定义v-model对应的事件名
},
props: { checked: { type: Boolean, default: false } }
}
父组件使用时仍保持v-model语法:
vue
<template>
<CustomSwitch v-model="isOpen" />
</template>
3. .sync:非表单组件的双向同步语法糖
.sync是v-bind:属性 + v-on:update:属性的语法糖,适用于非表单组件(如弹窗标题、组件状态)的双向同步。
vue
<!-- 父组件 -->
<template>
<Dialog :title.sync="dialogTitle" />
</template>
<script>
export default { data() { return { dialogTitle: '默认标题' } } }
</script>
<!-- 子组件 -->
<template>
<h3>{{ title }}</h3>
<button @click="$emit('update:title', '新标题')">修改标题</button>
</template>
<script>
export default { props: { title: String } }
</script>
4. ref + $refs:直接访问子组件实例
ref是模板中的标记属性,$refs是组件实例的内置对象------父组件通过ref标记子组件后,可直接访问其数据、方法。
vue
<template>
<Child ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script>
export default {
methods: {
callChildMethod() {
const child = this.$refs.childRef;
child.resetData(); // 调用子组件方法
console.log(child.childData); // 访问子组件数据
}
},
mounted() {
// 需在组件挂载后访问$refs
}
}
</script>
注意:此方式破坏组件封装性,仅建议用于"主动调用子组件方法(如表单校验)"等应急场景。
5. parent / children:自动获取亲属实例
$parent:子组件直接访问父组件实例;$children:父组件访问子组件实例列表(按渲染顺序排列的数组)。
javascript
// 子组件访问父组件
export default {
mounted() {
console.log(this.$parent.parentData);
this.$parent.parentMethod();
}
}
// 父组件访问子组件
export default {
mounted() {
this.$children[0].childMethod(); // 调用第一个子组件的方法
}
}
缺点 :$children顺序不稳定,组件结构变化时易失效,强耦合,不推荐复杂项目使用。
二、跨层级组件通信:深层嵌套的协作
当组件嵌套层级较深(如"页面→布局→卡片→按钮"),父子通信的"层层传递"会冗余,需用跨层级方式。
1. attrs + listeners:属性与事件的透传
$attrs:存储父组件传递的、未被当前组件props接收的所有属性(不含class/style);$listeners:存储父组件绑定的所有事件。
二者结合实现属性/事件"透传",无需手动层层传递:
vue
<!-- 中间组件(透传) -->
<template>
<DeepChild v-bind="$attrs" v-on="$listeners" />
</template>
<script>
export default { inheritAttrs: false } // 关闭根元素自动绑定$attrs
</script>
场景:封装UI组件库时,将外部属性/事件透传给原生元素。
2. provide + inject:祖孙组件的直接通信
祖先组件通过provide提供数据/方法,任意后代组件通过inject注入使用,无需中间组件中转。
(1)Vue2用法(含响应式)
Vue2中provide默认非响应式,需配合Vue.observable实现响应式:
javascript
// 祖先组件
export default {
provide() {
const reactiveData = Vue.observable({ name: '张三' }); // 响应式数据
return { reactiveData };
}
}
// 后代组件
export default {
inject: ['reactiveData'],
mounted() { console.log(this.reactiveData.name); } // 张三(响应式)
}
(2)Vue3用法(天然响应式)
Vue3中provide可直接传递ref/reactive对象:
javascript
// 祖先组件(组合式API)
import { provide, reactive } from 'vue';
export default {
setup() {
const user = reactive({ name: '张三' });
provide('user', user);
}
}
// 后代组件(组合式API)
import { inject } from 'vue';
export default {
setup() {
const user = inject('user');
return { user };
}
}
三、无关联组件通信:任意组件的状态共享
对于无直接关系的组件(如兄弟、跨页面组件),需用全局级通信方式。
1. 全局事件总线($bus):中小型项目轻量方案
通过给Vue原型挂载全局Vue实例($bus),实现"发布-订阅"式通信:
(1)挂载总线(main.js)
javascript
import Vue from 'vue';
Vue.prototype.$bus = new Vue(); // 挂载全局总线
(2)订阅与发布
javascript
// 接收方组件
export default {
mounted() {
this.$bus.$on('sendMsg', (msg) => { console.log('收到:', msg); });
},
beforeDestroy() {
this.$bus.$off('sendMsg'); // 组件销毁前解绑,避免内存泄漏
}
}
// 发送方组件
export default {
methods: {
send() { this.$bus.$emit('sendMsg', 'Hello 全局总线'); }
}
}
2. 全局状态管理:大型项目规范方案
当多组件共享复杂状态(如用户信息、购物车),需用全局状态管理工具集中管理。
(1)Vue2:Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库 。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。核心概念包括state(存数据)、mutations(同步改数据)、actions(异步操作)、Getter(计算属性)、Model(模块):
javascript
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: { user: { name: '张三' } },
mutations: {
updateUser(state, newUser) { state.user = newUser; }
},
actions: {
fetchUser({ commit }) {
setTimeout(() => commit('updateUser', { name: '李四' }), 1000);
}
}
});
组件中使用:
javascript
// 读取状态
this.$store.state.user;
// 触发mutation
this.$store.commit('updateUser', { name: '王五' });
// 触发action
this.$store.dispatch('fetchUser');
(2)Vue3:Pinia(替代Vuex)
Pinia更轻量,无mutations层,直接用actions处理同步/异步:
javascript
// store/user.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({ user: { name: '张三' } }),
actions: {
updateUser(newUser) { this.user = newUser; },
async fetchUser() {
const res = await new Promise(resolve => setTimeout(() => resolve({ name: '李四' }), 1000));
this.user = res;
}
}
});
组件中使用(组合式API):
javascript
import { useUserStore } from '@/store/user';
export default {
setup() {
const userStore = useUserStore();
userStore.updateUser({ name: '王五' });
userStore.fetchUser();
return { user: userStore.user };
}
}
3. 路由参数(vue-router):页面级组件通信
通过query(URL明文)或params(路径参数)传递数据,实现路由页面间通信:
javascript
// 跳转页传递参数
this.$router.push({ path: '/detail', query: { id: 1 } });
// 接收页获取参数
this.$route.query.id; // 1
1. query 方式(URL 明文传参)
路由配置
javascript
const routes = [{ path: '/detail', name: 'Detail', component: Detail }]
声明式跳转
vue
<router-link :to="{ path: '/detail', query: { 键1: 值1, 键2: 值2 } }">跳转</router-link>
<!-- 或用name跳转 -->
<router-link :to="{ name: 'Detail', query: { 键1: 值1, 键2: 值2 } }">跳转</router-link>
编程式跳转
javascript
this.$router.push({ path: '/detail', query: { 键1: 值1, 键2: 值2 } })
// 或用name跳转
this.$router.push({ name: 'Detail', query: { 键1: 值1, 键2: 值2 } })
接收参数
javascript
this.$route.query.键名
2. params 方式(路径占位符传参)
路由配置
javascript
const routes = [{ path: '/detail/:必填键/:可选键?', name: 'Detail', component: Detail }]
声明式跳转
vue
<router-link :to="{ name: 'Detail', params: { 键1: 值1, 键2: 值2 } }">跳转</router-link>
编程式跳转
javascript
this.$router.push({ name: 'Detail', params: { 键1: 值1, 键2: 值2 } })
接收参数
javascript
this.$route.params.键名
4. 本地存储:跨页面/组件的持久化通信
通过localStorage/sessionStorage存储数据,实现跨页面/组件通信:
javascript
// 存储(对象需序列化)
localStorage.setItem('user', JSON.stringify({ name: '张三' }));
// 读取(反序列化)
const user = JSON.parse(localStorage.getItem('user'));
四、选型指南:选对方式事半功倍
| 组件关系 | 推荐方案 | 适用场景 |
|---|---|---|
| 父子组件 | props + $emit、v-model | 表单组件、简单数据传递 |
| 跨层级组件 | provide + inject、$attrs | 深层嵌套组件、全局配置 |
| 无关联组件 | Pinia(Vue3)/Vuex(Vue2) | 多组件共享状态、跨页面通信 |
| 页面级组件 | 路由参数 | 路由跳转传参 |
实战建议
- 小型项目:用
props+$emit、全局事件总线即可; - 中大型项目:优先用Pinia/Vuex管理全局状态;
- 封装UI组件:用
props+$emit、$attrs+$listeners保证通用性。