Vue 98 ,Vue3 组件通信实践:父传子、子传父、兄弟组件传值,以及组件懒加载完整指南( Vue3 最新 API 重点提炼,附实战案例 )
目录
前言
一、父传子
1.1 基础:传递简单数据(字符串 / 布尔 / 数字)
父组件:绑定属性
子组件:defineProps 接收 + 校验
1.2 进阶:传递复杂数据(对象 / 数组)
父组件:传递对象
子组件:接收 + 监听更新
1.3 父传子核心注意事项
二、子传父
2.1 基础:defineEmits 触发事件传值
子组件:定义事件 + 触发传值
父组件:监听事件 + 接收数据
2.2 进阶:v-model 简化双向绑定
Vue3 v-model 核心协议
实战:v-model 控制弹框
子组件适配
父组件简化绑定
扩展:多 v-model 绑定
2.3 特殊:defineExpose 暴露方法
子组件:暴露方法
父组件:引用并调用
三、兄弟组件传值
3.1 父组件中转(✅ 使用最频繁)
示例结构
父组件(中转)
子组件 A(发出事件)
子组件 B(接收数据)
3.2 Pinia 状态管理(⚙️ 中大型项目常用)
① 安装依赖
② 全局注册
③ 定义仓库
④ 子组件 A(更新状态)
⑤ 子组件 B(共享状态)
3.3 Mitt 事件总线(📡 临时通信用)
① 安装依赖
② 创建事件总线
③ 子组件 A(发送事件)
④ 子组件 B(监听事件)
3.4 兄弟通信总结与最佳实践
四、常见误区与最佳实践
4.1 避坑指南
4.2 最佳实践
五、本文总结

前言
Vue3 基于 <script setup> 语法的父子通信,核心围绕 defineProps(父传子)、defineEmits(子传父)、v-model(双向绑定) 和 defineExpose(子组件暴露方法),相比 Vue2 更简洁、类型友好。本文提炼实战核心,聚焦 “关键用法 + 极简示例 + 避坑点”,帮助快速掌握核心逻辑。

一、父传子
父传子:defineProps 核心用法
核心逻辑:父组件通过「属性绑定」传值,子组件通过 defineProps 接收并校验,支持简单数据、复杂数据传递。
1.1 基础:传递简单数据(字符串 / 布尔 / 数字)
父组件:绑定属性
vue
<template>
<!-- 绑定 title(字符串)、isDisabled(布尔) -->
<ChildComp :title="dialogTitle" :is-disabled="isLoading" />
</template>
<script setup>
import { ref } from 'vue';
import ChildComp from './ChildComp.vue';
const dialogTitle = ref('新增立项'); // 要传递的字符串
const isLoading = ref(false); // 要传递的布尔值
</script>
组件懒加载:
import { ref, reactive, defineAsyncComponent } from 'vue'
// 懒加载弹框组件
// 只有当组件被需要时(dialogVisible为true)才会加载
const ChildComp = defineAsyncComponent(() => import('./ChildComp.vue'))
子组件:defineProps 接收 + 校验
vue
<template>
<div>
<h3>{{ title }}</h3>
<button :disabled="isDisabled">提交</button>
</div>
</template>
<script setup>
// 接收并校验父组件数据(类型、必填、默认值)
const props = defineProps({
title: {
type: String, // 明确类型
required: true, // 必填(缺省会警告)
validator: (val) => val.length >= 2 // 自定义验证(标题长度≥2)
},
isDisabled: {
type: Boolean,
required: false,
default: false // 简单类型默认值直接写
}
});
</script>
1.2 进阶:传递复杂数据(对象 / 数组)
复杂数据需注意「响应式监听」和「避免引用污染」。
父组件:传递对象
vue
<template>
<ChildComp :form-init="currentForm" />
</template>
<script setup>
import { ref } from 'vue';
const currentForm = ref({ name: '企业采购', value: 500000 }); // 复杂对象
</script>
子组件:接收 + 监听更新
vue
<template>
<el-input v-model="localForm.name" />
</template>
<script setup>
import { ref, reactive, watch } from 'vue';
const props = defineProps({
formInit: {
type: Object,
required: false,
default: () => ({}) // 复杂类型默认值:必须用函数返回(避免共享引用)
}
});
// 子组件本地数据(不直接改 props)
const localForm = reactive({});
// 监听父组件数据变化,同步到本地
watch(
() => props.formInit,
(newVal) => {
Object.assign(localForm, newVal); // 合并更新,保持响应式
},
{ immediate: true } // 初始渲染立即执行
);
</script>
1.3 父传子核心注意事项
- 复杂类型(Object/Array)默认值必须用
() => 值函数返回,避免多组件共享引用; - 子组件禁止直接修改 props(会触发警告),需同步到本地数据修改;
- 属性名:父组件用
kebab-case(如is-disabled),子组件用camelCase(如isDisabled),Vue 自动映射。
二、子传父
子传父:3 种核心方式
核心逻辑:子组件通过「事件触发」传值,父组件「监听事件」接收;v-model 简化双向绑定,defineExpose 支持父组件主动调用子方法。
2.1 基础:defineEmits 触发事件传值
子组件:定义事件 + 触发传值
vue
<template>
<button @click="handleSubmit">提交</button>
</template>
<script setup>
import { reactive } from 'vue';
// 1. 定义要触发的事件(支持多个)
const emit = defineEmits(['close', 'submit']);
// 子组件本地数据
const formData = reactive({ name: '', value: null });
// 2. 触发事件:emit('事件名', 要传递的数据)
const handleSubmit = () => {
emit('submit', { ...formData }); // 解构传递(避免父组件改子组件引用)
emit('close'); // 无参数事件
};
</script>
父组件:监听事件 + 接收数据
vue
<template>
<ChildComp @submit="onSubmit" @close="onClose" />
</template>
<script setup>
const onSubmit = (childData) => {
console.log('子组件传递的表单数据:', childData); // 接收子组件数据
// 调用接口等业务逻辑...
};
const onClose = () => {
console.log('子组件触发关闭');
};
</script>
2.2 进阶:v-model 简化双向绑定
适用于「子组件控制父组件状态」(如弹框显示 / 隐藏),替代 “:状态+@update” 的繁琐写法。
Vue3 v-model 核心协议
- 父组件:
<Child v-model:状态名="父状态" /> - 子组件:接收
状态名→ 触发update:状态名事件传新值
实战:v-model 控制弹框
子组件适配
vue
<template>
<el-dialog :visible="visible" @close="handleClose">内容</el-dialog>
</template>
<script setup>
// 1. 接收父组件 v-model 传递的状态(visible)
const props = defineProps({
visible: {
type: Boolean,
default: false
}
});
// 2. 定义 emit,需包含 update:visible 事件
const emit = defineEmits(['update:visible']);
// 3. 关闭时触发事件,通知父组件更新状态
const handleClose = () => {
emit('update:visible', false); // 传递新值(false)
};
</script>
父组件简化绑定
vue
<template>
<!-- v-model:visible 直接绑定状态,无需额外监听 close 事件 -->
<ChildComp v-model:visible="dialogVisible" />
<button @click="dialogVisible = true">打开弹框</button>
</template>
<script setup>
import { ref } from 'vue';
const dialogVisible = ref(false);
</script>
扩展:多 v-model 绑定
父组件可同时绑定多个子组件状态:
vue
<!-- 父组件:绑定 visible 和 submitLoading 两个状态 -->
<ChildComp
v-model:visible="dialogVisible"
v-model:submit-loading="isLoading"
/>
2.3 特殊:defineExpose 暴露方法
适用于「父组件主动调用子组件方法」(如强制清空表单、触发验证)。
子组件:暴露方法
vue
<script setup>
import { ref } from 'vue';
// 子组件内部方法
const resetForm = () => {
console.log('清空表单');
// 实际清空逻辑...
};
// 暴露方法(父组件可通过 ref 调用)
defineExpose({ resetForm });
</script>
父组件:引用并调用
vue
<template>
<ChildComp ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue';
// 1. 给子组件加 ref 标识
const childRef = ref();
// 2. 调用子组件暴露的方法
const callChildMethod = () => {
childRef.value?.resetForm(); // 可选链避免报错
};
</script>
三、兄弟组件传值
兄弟组件传值(按使用频率排序)
在 Vue3 中,兄弟组件(非父子关系)之间不能直接使用 props 或 emit 传值。
常见的通信方式主要有 父组件中转 → Pinia 状态共享 → Mitt 事件总线 三种。
以下按照 实际使用频率 从高到低讲解。
3.1 父组件中转(✅ 使用最频繁)
最推荐方式,逻辑清晰、类型安全、无依赖。
核心思路:通过父组件作为“桥梁”,子 A 发事件 → 父组件接收 → 再传给子 B。
示例结构
Parent
├─ ChildA(发出数据)
└─ ChildB(接收数据)
父组件(中转)
<template>
<div>
<ChildA @select-item="handleSelect" />
<ChildB :selected-item="selectedItem" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import ChildA from './ChildA.vue';
import ChildB from './ChildB.vue';
const selectedItem = ref(null);
const handleSelect = (item) => {
selectedItem.value = item;
};
</script>
子组件 A(发出事件)
<template>
<el-button @click="chooseItem('项目管理')">选择项目管理</el-button>
</template>
<script setup>
const emit = defineEmits(['select-item']);
const chooseItem = (val) => emit('select-item', val);
</script>
子组件 B(接收数据)
<template>
<div>当前选择:{{ selectedItem }}</div>
</template>
<script setup>
const props = defineProps({
selectedItem: String
});
</script>
✅ 优点
-
数据流清晰;
-
无需额外依赖;
-
适用于多数业务组件间通信。
⚠️ 注意
-
若兄弟层级过深或数量多,父组件容易成为“中转站混乱点”,可用 Pinia 优化。
3.2 Pinia 状态管理(⚙️ 中大型项目常用)
首先,Pinia 不是 Vue3 默认内置的,需要单独安装依赖并在项目中注册。它是Vue3 官方推荐的全局状态库,适用于复杂通信、多页面共享数据场景。
① 安装依赖
在项目根目录运行以下命令(确保 Node 环境已安装):
npm install pinia
如果你使用 Vue CLI 或 Vite 创建的项目,这一步只需要执行一次。
② 全局注册
在项目中注册 Pinia(main.js):必须在 Vue 应用入口文件中进行注册,才能在各组件中正常使用
// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount('#app');
③ 定义仓库
// stores/useProjectStore.js
import { defineStore } from 'pinia';
export const useProjectStore = defineStore('project', {
state: () => ({
selectedItem: ''
}),
actions: {
setItem(val) {
this.selectedItem = val;
}
}
});
④ 子组件 A(更新状态)
<script setup>
import { useProjectStore } from '@/stores/useProjectStore';
const store = useProjectStore();
const handleSelect = (val) => {
store.setItem(val);
};
</script>
<template>
<el-button @click="handleSelect('系统管理')">选择系统管理</el-button>
</template>
⑤ 子组件 B(共享状态)
<script setup>
import { useProjectStore } from '@/stores/useProjectStore';
const store = useProjectStore();
</script>
<template>
<div>当前选择:{{ store.selectedItem }}</div>
</template>
✅ 优点
-
状态集中管理;
-
响应式共享;
-
模块化、可调试。
⚠️ 注意
-
不要滥用全局状态,应仅在确实需要共享的核心数据中使用;
-
在大型项目中,建议模块化拆分 store。
3.3 Mitt 事件总线(📡 临时通信用)
Vue3 中已移除
$emit/$on全局事件总线机制,
但可用轻量库 Mitt 实现类似功能。
① 安装依赖
npm install mitt
② 创建事件总线
// utils/eventBus.js
import mitt from 'mitt';
export const eventBus = mitt();
③ 子组件 A(发送事件)
<script setup>
import { eventBus } from '@/utils/eventBus';
const sendMsg = () => eventBus.emit('updateItem', '合同管理');
</script>
<template>
<el-button @click="sendMsg">发送消息</el-button>
</template>
④ 子组件 B(监听事件)
<script setup>
import { eventBus } from '@/utils/eventBus';
import { ref, onMounted, onBeforeUnmount } from 'vue';
const item = ref('');
onMounted(() => {
eventBus.on('updateItem', (val) => (item.value = val));
});
onBeforeUnmount(() => {
eventBus.off('updateItem'); // 组件销毁时清理监听
});
</script>
<template>
<div>接收到:{{ item }}</div>
</template>
✅ 优点
-
实现简单;
-
组件层级无关;
-
适合临时广播、消息提示。
⚠️ 注意
-
必须在组件卸载时
off()移除监听; -
不推荐长期持有全局状态;
-
建议仅限小项目或独立模块使用。
3.4 兄弟通信总结与最佳实践
| 使用方式 | 适用场景 | 优点 | 注意事项 |
|---|---|---|---|
| 父组件中转 | 普通兄弟组件通信 | 简单直观、零依赖 | 层级过多会臃肿 |
| Pinia 状态共享 | 多模块、大型项目 | 状态集中、响应式 | store 拆分与命名管理 |
| Mitt 事件总线 | 临时广播、解耦通信 | 轻量灵活 | 需手动清理事件监听 |
🧩 推荐选型:
90% 场景 → 父组件中转;
多模块共享状态 → Pinia;
临时事件广播 → Mitt。
四、常见误区与最佳实践
4.1 避坑指南
- 禁止子组件直接改 props:必须通过
emit通知父组件修改; - 复杂类型默认值错写:Object/Array 需用
() => 值,否则多组件共享数据; v-model事件名错写:子组件必须触发update:状态名(如update:visible),而非自定义名;- 子组件方法未暴露:
<script setup>中方法默认私有,需defineExpose才能被父组件调用。
4.2 最佳实践
- 优先用
props + emit:低耦合,覆盖 90% 场景; - 状态双向同步用
v-model:简化弹框、输入框等场景代码; - 复杂数据传递用「浅拷贝」:子传父时用
{ ...data },避免引用污染; - 明确类型校验:
defineProps写清type和required,减少运行时错误。
五、本文总结
Vue3 父子通信核心是「API 简洁化 + 职责清晰化」:
- 父传子:
defineProps接收 + 校验,复杂数据用watch同步;- 子传父:基础用
defineEmits触发事件,双向状态用v-model,主动调用用defineExpose;- 核心原则:不直接改 props、明确类型、减少耦合,适配中后台系统高频场景(如弹框、表单)。

更多推荐



所有评论(0)