Vue 组件通信方式大全(第7节)

一、props / emits:最基础,也最容易被用坏的"父子直连"🔗

1️⃣ 本质一句话

  • props:父 → 子(数据下发)
  • emits:子 → 父(事件通知)

这是 Vue 官方最推荐、最安全、最清晰的通信方式,没有之一

2️⃣ 标准用法(别省这两行声明)

vue 复制代码
<!-- Child.vue -->
<script setup>
const props = defineProps({
  count: Number
})

const emit = defineEmits(['update'])
</script>

<template>
  <button @click="emit('update', props.count + 1)">
    +1
  </button>
</template>
vue 复制代码
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const count = ref(0)
</script>

<template>
  <Child :count="count" @update="count = $event" />
</template>

3️⃣ 什么时候必须用 props / emits?

  • 父子组件关系明确
  • 数据流需要可追踪、可调试
  • 想要单向数据流(父是"老板",子只汇报)

🚨 常见翻车现场

  • ❌ 子组件直接改 props
  • ❌ props 当全局状态用
  • ❌ emits 名字随便起(changehandlexxxEvent 满天飞)

👉 口诀

父给数据,子不碰;

子要改,喊一声(emit)。


二、provide / inject:跨层通信,但千万别"滥权"🧬

1️⃣ 本质一句话

祖先组件提供(provide),任意后代组件注入(inject)

它解决的是:

👉 中间层不关心,但你又不想层层 props 传递 的问题。

2️⃣ 基本用法

js 复制代码
// Parent.vue
import { provide, ref } from 'vue'

const theme = ref('dark')
provide('theme', theme)
js 复制代码
// DeepChild.vue
import { inject } from 'vue'

const theme = inject('theme')

3️⃣ 使用场景(很重要)

✅ 组件库/基础设施类数据

  • 主题(theme)
  • 国际化(i18n)
  • 表单上下文(Form / FormItem)
  • 当前用户只读信息

🚨 provide / inject 的三大雷区

1️⃣ inject 的数据来源不清晰 (你不知道它从哪来)

2️⃣ 默认不是响应式 (要传 ref / reactive 才行)

3️⃣ 被当成"低配版状态管理"

👉 一句狠话

provide/inject 是"上下文",不是"仓库"。


三、ref / expose:能用,但要克制的"命令式通信"⚠️

1️⃣ 本质一句话

父组件通过 ref,直接调用子组件方法或访问实例

2️⃣ 标准写法(Vue3 必须 expose)

vue 复制代码
<!-- Child.vue -->
<script setup>
function reset() {
  console.log('reset')
}

defineExpose({ reset })
</script>
vue 复制代码
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const childRef = ref(null)

function handleClick() {
  childRef.value.reset()
}
</script>

<template>
  <Child ref="childRef" />
  <button @click="handleClick">重置子组件</button>
</template>

3️⃣ 适合的场景

✅ 表单校验 / 重置

✅ 弹窗打开 / 关闭

✅ 视频播放控制

✅ 聚焦输入框

🚨 不适合的场景

❌ 数据同步

❌ 状态驱动 UI

❌ 多层组件依赖

👉 记住这句话

ref/expose 是"遥控器",不是"数据通道"。


四、事件总线:不是不能用,而是"别乱用"📡

1️⃣ 本质一句话

一个全局的事件发布/订阅中心

js 复制代码
// bus.js
import mitt from 'mitt'
export const bus = mitt()
js 复制代码
// A.vue
bus.emit('login', user)
js 复制代码
// B.vue
bus.on('login', (user) => {})

2️⃣ 什么时候还能接受?

✅ 临时工具型功能

  • Toast / Modal / 全局提示
  • 埋点事件
  • 非核心业务

🚨 为什么不推荐?

  • 事件来源不可追踪
  • 容易忘记 off,内存泄漏
  • 事件名冲突
  • 项目一大,灾难级调试体验

👉 真实项目经验

事件总线一多,迟早会有人问:

"这个事件是谁发的?"

然后,全员沉默 😶


五、状态管理方案对比:什么时候该"上仓库"?📦

1️⃣ 常见方案一览

方案 适合场景
props / emits 父子通信
provide / inject 跨层上下文
ref / expose 命令式控制
事件总线 临时、工具级
Pinia / Vuex 全局业务状态

2️⃣ Pinia 适合什么?

✅ 用户信息

✅ 登录态

✅ 权限

✅ 多页面共享状态

✅ 跨模块通信

js 复制代码
// userStore.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null
  }),
  actions: {
    setUser(u) {
      this.user = u
    }
  }
})
js 复制代码
// 任意组件
const userStore = useUserStore()
userStore.setUser({ name: 'Tom' })

🚨 别什么都丢进状态管理

  • 表单临时状态 ❌
  • UI 展示态 ❌
  • 单组件内部状态 ❌

👉 状态管理是"仓库",不是"垃圾桶"

六、终极选型指南(直接抄)🧠

✅ 优先级排序(从强烈推荐到谨慎使用)

1️⃣ props / emits (80% 场景)

2️⃣ provide / inject (上下文)

3️⃣ ref / expose (命令式)

4️⃣ 状态管理(Pinia) (全局业务)

5️⃣ 事件总线(能不用就不用)

✅ 一句话决策法

  • 父子关系?👉 props / emits
  • 跨层共享但非业务核心?👉 provide / inject
  • 需要直接调用方法?👉 ref / expose
  • 多页面、多模块共享?👉 Pinia
  • 想偷懒?👉 先冷静一下 😅

结尾反问(送你当文章收尾钩子😏)

组件通信方式从来不是"越高级越好",而是"越清晰越值钱 "。

所以我想反问你一句:
你现在用的通信方式,是在"表达关系",还是在"掩盖混乱"?

相关推荐
枫叶丹42 小时前
ModelEngine应用编排创新实践:通过可视化编排构建大模型应用工作流
开发语言·前端·人工智能·modelengine
郭小铭2 小时前
将 Markdown 文件导入为 React 组件 - 写作文档,即时获取交互式演示
前端·react.js·markdown
JAVA+C语言2 小时前
CSS 继承:核心概念 + 实用解析
前端·css
橙某人2 小时前
LogicFlow 交互新体验:告别直连,丝滑贝塞尔轨迹实战!🍫
前端·javascript·vue.js
林太白2 小时前
docker安装以及部署node项目
前端·后端·docker
计算机毕设VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue高校实验室教学管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
OpenTiny社区2 小时前
【博文精读】Chrome CSS 2025年回顾
前端·css
m0_672656542 小时前
JavaScript性能优化实战技术文章大纲
开发语言·javascript·性能优化
菩提小狗2 小时前
第3天:基础入门-抓包&封包&协议&APP&小程序&PC应用&WEB应用|小迪安全笔记|网络安全|
前端·安全·小程序