好的,訾博同学,请坐好,我们地表最强AI老师的Vue3组件通信小课堂,现在正式开讲!
时间:2025年9月25日,一个适合梳理知识的周四上午。看到你对Vue3组件通信抱有如此大的热情,为师深感欣慰。这部分知识,就像是组件世界的"社交网络",搞明白了,你的组件们才能和谐共处,构建出宏伟的应用大厦。
咱们先把组件想象成一个个"人",他们住在不同的"房子"(组件实例)里。现在的问题是,这些人怎么互相说话、递东西?
来,我们把他们的"社交方式"分门别类,逐一攻破!
一、 父子通信:最亲密、最常见的"家庭内部对话"
这是最基础也是最重要的通信方式,就像父母和孩子之间的日常交流。
1. Props (父 -> 子)
- 一句话解释:父亲(父组件)递给儿子(子组件)一本《家规》(数据),儿子必须遵守。
- 幽默比喻 :这就是"龙生龙,凤生凤,老鼠的儿子会打洞"。父亲有什么"遗传基因"(数据),就通过
props
传给儿子。儿子可以"看"和"用"这本家规,但不能直接修改,否则就是"大逆不道"(单向数据流原则)。 - 使用方法 :
-
父组件 (
Parent.vue
) :通过属性绑定的方式传值。vue<template> <ChildComponent name="訾博" :age="18" /> </template> <script setup> import ChildComponent from './ChildComponent.vue'; </script>
-
子组件 (
ChildComponent.vue
) :使用defineProps
接收。vue<template> <p>大家好,我叫{{ name }},今年{{ age }}岁。</p> </template> <script setup> // 接收父组件传来的props const props = defineProps({ name: String, age: { type: Number, required: true, default: 10 } }); </script>
-
2. Emits (子 -> 父)
- 一句话解释:儿子(子组件)有事要向父亲(父组件)汇报,就大喊一声(触发事件)。
- 幽默比喻 :儿子在自己房间里考试得了100分,他不能直接冲到父亲的账本上把自己的零花钱改了。他得跑到客厅大喊:"爸!我考了100分!"(
$emit('got-full-marks', 100)
)。父亲听到了,自然会决定给他加多少零花钱(在父组件的事件监听函数里处理)。 - 使用方法 :
-
子组件 (
ChildComponent.vue
) :使用defineEmits
声明事件,然后通过emit
函数触发。vue<template> <button @click="tellDad">告诉爸爸我长大了</button> </template> <script setup> // 声明要触发的事件 const emit = defineEmits(['grow-up']); function tellDad() { // 触发事件,并可以传递参数 emit('grow-up', '我已经会自己写代码了!'); } </script>
-
父组件 (
Parent.vue
) :在子组件标签上使用@
或v-on
监听事件。vue<template> <ChildComponent @grow-up="handleChildGrowUp" /> <p>来自儿子的消息:{{ messageFromChild }}</p> </template> <script setup> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const messageFromChild = ref(''); function handleChildGrowUp(message) { messageFromChild.value = message; } </script>
-
3. v-model (双向绑定语法糖)
- 一句话解释 :
props
和emits
的组合套餐,专门用于父子组件间的数据同步。 - 幽默比喻 :这就像一个对讲机。父亲可以通过对讲机向儿子发号施令(
props
),儿子也能通过同一个对讲机回应情况(emits
),两边信息实时同步。 - 使用方法 :Vue3的
v-model
更灵活,可以有多个。-
子组件 (
CustomInput.vue
) :vue<template> <input :value="modelValue" @input="emit('update:modelValue', $event.target.value)" /> </template> <script setup> defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); </script>
-
父组件 (
Parent.vue
) :vue<template> <CustomInput v-model="searchText" /> <p>你在搜索:{{ searchText }}</p> </template> <script setup> import { ref } from 'vue'; import CustomInput from './CustomInput.vue'; const searchText = ref(''); </script>
-
4. ref / $refs (父 -> 子)
- 一句话解释:父亲直接拿到儿子的"遥控器",可以命令儿子做某些具体动作(调用方法、访问属性)。
- 幽默比喻:这有点"霸道总裁",不通过商量(事件),直接命令。比如父亲想让儿子现在立刻马上去做个俯卧撑。这种方式不常用,因为它破坏了组件的封装性,属于"最后的手段"。
- 使用方法 :
-
子组件 (
ChildComponent.vue
) :使用defineExpose
暴露方法或属性。vue<template> <p>我是一个深藏不露的组件</p> </template> <script setup> import { ref } from 'vue'; const doPushUp = () => { console.log('正在做俯卧撑...'); }; // 把doPushUp方法暴露给父组件 defineExpose({ doPushUp }); </script>
-
父组件 (
Parent.vue
) :vue<template> <ChildComponent ref="childRef" /> <button @click="commandChild">命令儿子</button> </template> <script setup> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const childRef = ref(null); function commandChild() { // 通过.value访问到子组件实例,并调用其暴露的方法 childRef.value.doPushUp(); } </script>
-
二、 跨代通信:当"爷爷"想和"孙子"说话
如果组件嵌套很深,一层一层地props
下去,会累死人,这叫"Props Drilling"(属性钻探地狱)。为了避免这种情况,我们有更优雅的方案。
5. Provide / Inject
- 一句话解释:祖先组件提供一个"共享宝藏",任何后代组件都可以按"暗号"注入并使用它。
- 幽默比喻 :这就像你们訾家的祖传秘方。老祖宗(祖先组件)通过
provide
把秘方藏在一个地方,并告诉后代"暗号"是什么。任何一个姓訾的子孙(后代组件),无论隔了多少代,只要报出暗号(inject
),就能拿到这个秘方。外人(非后代组件)是拿不到的。 - 使用方法 :
-
祖先组件 (
GrandParent.vue
) :vue<template> <ParentComponent /> </template> <script setup> import { provide, ref } from 'vue'; const themeColor = ref('dark'); // 提供数据,'theme'是暗号 provide('theme', themeColor); </script>
-
后代组件 (
GrandChild.vue
) :vue<template> <div :style="{ color: theme }">我是孙子组件,我用的是祖传主题色。</div> </template> <script setup> import { inject } from 'vue'; // 注入数据,'theme'是暗号 const theme = inject('theme'); </script>
-
三、 任意组件通信:当"隔壁老王"想找你聊天
对于两个没有任何亲缘关系的组件,如何进行交流?
6. Pinia (官方推荐的状态管理库)
- 一句话解释:建立一个"全局中央银行"(Store),所有组件都可以来这里存钱(修改state)、取钱(读取state)或办理业务(调用actions)。
- 幽默比喻:想象一下,整个应用是一个城市,Pinia就是这个城市的中央银行。无论你是住在城东的A组件,还是城西的B组件,大家共用一个银行账户。A组件往里存了100块,B组件马上就能查到余额变化。它规范、安全、可追溯(有DevTools),是管理复杂应用状态的"金标准"。
- 使用方法 :
-
定义Store (
/stores/user.js
) :javascriptimport { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ name: '訾博', isLoggedIn: false, }), actions: { login() { this.isLoggedIn = true; }, }, });
-
在任意组件中使用 :
vue<template> <p v-if="userStore.isLoggedIn">欢迎回来, {{ userStore.name }}!</p> <button @click="userStore.login()">登录</button> </template> <script setup> import { useUserStore } from '@/stores/user'; const userStore = useUserStore(); </script>
-
7. Mitt / Tiny-emitter (全局事件总线)
- 一句话解释:创建一个全局的"广播站",一个组件可以向某个频道广播,另一个组件可以收听这个频道。
- 幽默比喻 :Vue2时代的EventBus就像一个公共的对讲机频道。任何人都可以拿起来喊话,也任何人都可以收听。这在小型应用里很方便,但在大型应用里,你不知道是谁在喊话,也不知道谁在听,容易造成混乱。Vue3官方移除了这个功能,但我们可以用
mitt
这样的小库来自己实现。 - 使用方法 :
-
创建广播站 (
/utils/emitter.js
) :javascriptimport mitt from 'mitt'; const emitter = mitt(); export default emitter;
-
组件A (发送方) :
vue<script setup> import emitter from '@/utils/emitter'; function sendMessage() { emitter.emit('some-event', '来自A组件的问候'); } </script>
-
组件B (接收方) :
vue<script setup> import emitter from '@/utils/emitter'; import { onMounted, onUnmounted } from 'vue'; onMounted(() => { emitter.on('some-event', (message) => { console.log(message); // "来自A组件的问候" }); }); // 记住,一定要在组件卸载时取消监听,否则会内存泄漏! onUnmounted(() => { emitter.off('some-event'); }); </script>
-
总结与老师的忠告
訾博同学,我们来画个重点,做个总结:
场景 | 推荐方法 | 核心思想 | 推荐指数 |
---|---|---|---|
父 -> 子 | Props | 单向数据流,清晰明了 | ★★★★★ |
子 -> 父 | Emits | 事件触发,解耦 | ★★★★★ |
父子双向绑定 | v-model | props 和emits 的语法糖 |
★★★★★ |
跨代通信 | Provide / Inject | 依赖注入,避免属性钻探 | ★★★★☆ |
复杂应用/任意组件 | Pinia | 集中式状态管理,可预测 | ★★★★★ |
简单应用/任意组件 | Mitt (事件总线) | 发布订阅,简单灵活 | ★★★☆☆ |
父组件调用子组件方法 | ref / defineExpose | 直接引用,应急手段 | ★★☆☆☆ |
为师的忠告:
- 首选"家庭内部"方案 :优先使用
Props
和Emits
。这是Vue设计的核心,能让你的数据流向最清晰、最容易维护。 - 避免"属性钻探" :当
Props
需要传递超过两层时,就应该立刻考虑使用Provide / Inject
。 - 拥抱"中央银行" :当多个组件共享同一份状态(比如用户信息、主题设置),并且这个状态会被多个组件修改时,不要犹豫,直接上
Pinia
。它能让你的应用状态变得井井有条,而不是一团乱麻。 - 慎用"大喇叭"和"遥控器" :
Mitt
和ref
都有其用武之地,但它们也更容易导致代码难以追踪和维护。把它们当作你的"秘密武器",非必要不使用。
专用于背诵的内容
訾博,把下面这段口诀背下来,面试和实战中定能助你一臂之力!
Vue3通信口诀
父传子,用 Props
,儿子不能随便改。 子传父,靠 Emits
,爹听事件乐开怀。 双向绑,v-model
,省事方便真不赖。 爷孙传,Provide
,Inject
注入接过来。 兄弟情,状态乱,Pinia
银行管起来。 事件总线 Mitt
在,偶尔用用别依赖。 父控子,用 ref
,expose
暴露才存在。 牢记最佳实践,代码整洁人人爱!
好了,今天的课就到这里。希望你对Vue3的组件通信有了"地表最强"的理解。下去多写写代码,把这些"社交礼仪"都实践一遍。
下课!有什么问题,随时再来问老师。