Vue 组件通信核心知识点梳理

在初学 Vue 时,对组件概念和通信逻辑一直似懂非懂。经过实际项目开发沉淀后,重新梳理组件全套核心知识点,包含组件意义、单文件组件、组件关系、全套通信方式,条理清晰,如果错误,欢迎指正。

一、为什么要使用组件?(组件化核心价值)

  1. 代码复用,减少重复开发:通用模块(按钮、弹窗、分页、卡片)只需编写一次,全局多处复用,大幅减少冗余代码。

  2. 代码拆分,结构清晰:将庞大页面按功能拆分为多个小组件,分而治之,代码层级清晰,便于阅读查找。

  3. 独立作用域,防止污染:组件 JS 变量私有、CSS 可通过 scoped 隔离,避免全局变量、样式冲突。

  4. 逻辑封装,降低维护成本:复杂业务逻辑封装在组件内部,外部只需调用即可,需求变更只需修改对应组件。

  5. 按需加载,优化页面性能:配合路由懒加载、异步组件,非首屏组件按需加载,减小首屏资源体积,提升页面渲染速度。

二、什么是单文件组件(SFC)

单文件组件是 Vue 工程化项目的标准开发方式,后缀为 .vue将一个组件的结构、逻辑、样式三合一封装在同一个文件中

一个标准单文件组件包含三部分:

  • <template>:书写页面 HTML 结构

  • <script setup>:书写组件 JS 业务逻辑、数据、方法、导入依赖

  • <style scoped>:书写当前组件专属样式(scoped 实现样式隔离)

三、组件之间的关系

项目中所有组件通信,都基于三种层级关系:

  1. 父子组件:组件直接嵌套引用(最常用)

  2. 兄弟组件:共用同一个父组件,平级互不嵌套

  3. 祖孙组件:多层嵌套(爷爷-父-孙子),隔层级关系

组件核心难点:不同关系的组件数据传递(通信)

四、父子组件通信(最核心)

1. 父传子:props 单向传值

原理

父组件通过自定义属性绑定数据,子组件通过 props 接收数据,单向数据流:只能父改子,子不能直接修改 props

代码示例

父组件 Parent.vue

vue 复制代码
<template>
  <!-- 动态绑定数据传给子组件 -->
  <Child :msg="title" :list="tableList" />
</template>

<script setup>
import Child from './Child.vue'
const title = '父组件传递的文字'
const tableList = [1,2,3]
</script>

子组件 Child.vue

vue 复制代码
<template>
  <div>
    <p>{{ msg }}</p>
    <p>{{ list }}</p>
  </div>
</template> 

<script setup>
// 简单接收
// const props = defineProps(['msg', 'list'])

// 项目规范写法:类型校验 + 必填 + 默认值
const props = defineProps({
  msg: {
    type: String,
    required: true
  },
  list: {
    type: Array,
    default: () => [] // 引用类型默认值必须通过函数返回
  }
})

console.log(props.msg)
</script>

2. 子传父:defineEmits 自定义事件

原理

子组件通过 defineEmits 声明自定义事件,调用 emit 触发事件并携带参数;父组件监听对应事件,接收子组件数据。

代码示例

子组件 Child.vue

vue 复制代码
<template>
  <button @click="sendData">向父组件传值</button>
</template>

<script setup>
// 声明自定义事件
const emit = defineEmits(['send'])

const sendData = () => {
  // 触发事件,携带多个参数传给父组件
  emit('send', '子组件的数据', 1232321)
}
</script>

父组件 Parent.vue

vue 复制代码
<template>
  <Child @send="getChildData" />
</template>

<script setup>
// 接收子组件传递的参数
const getChildData = (val1, val2) => {
  console.log(val1, val2) 
}
</script>

3. 父子双向绑定语法糖:defineModel

本质:props + emit 的语法糖,简化双向绑定代码,Vue3 推荐使用。

代码示例

子组件

vue 复制代码
<template>
  <input v-model="modelValue" />
</template>

<script setup>
// 快速实现双向绑定
const modelValue = defineModel()
</script>

父组件

vue 复制代码
<template>
  <Child v-model="num" />
</template>

<script setup>
const num = ref(0)
</script>

4. 父获取子实例/方法:ref + defineExpose

原理

默认子组件内部属性、方法对外封闭,子组件通过 defineExpose 主动暴露内容,父组件通过 ref 获取子组件实例,调用子组件变量和方法。

代码示例

子组件 Child.vue

vue 复制代码
<script setup>
import { ref } from 'vue'
const childMsg = ref('子内部变量')
const childFn = () => {
  alert('子组件方法')
}

// 主动暴露给父组件
defineExpose({ childMsg, childFn })
</script>

父组件 Parent.vue

vue 复制代码
<template>
  <Child ref="childRef" />
  <button @click="callChild">调用子方法</button>
</template>

<script setup>
import { ref } from 'vue'
const childRef = ref(null)

const callChild = () => {
  console.log(childRef.value.childMsg)
  childRef.value.childFn()
}
</script>

五、祖孙组件通信(跨层级)

祖孙组件多层嵌套,不推荐 props 逐层透传(中间组件冗余),主流两种方案:provide/inject、事件总线 mitt

1. provide / inject(Vue 原生首选)

核心原理
  • provide:祖先组件(爷爷)提供数据/方法,向下穿透所有后代组件

  • inject:任意后代组件直接注入使用,跳过中间所有层级

1.1 传递静态数据

爷爷组件

vue 复制代码
<template>
  <Father />
</template>

<script setup>
import { provide } from 'vue'
provide('grandMsg', '爷爷的静态文字')
</script>

孙子组件

vue 复制代码
<script setup>
import { inject } from 'vue'
const msg = inject('grandMsg')
console.log(msg)
</script>
1.2 传递响应式数据(项目常用)

普通静态数据无响应式,祖先更新数据后代不生效,需传递 ref 响应式数据。

爷爷组件

vue 复制代码
<script setup>
import { ref, provide } from 'vue'
const grandNum = ref(100)

// 提供响应式数据
provide('grandNum', grandNum)

// 提供修改数据的方法
const changeNum = () => {
  grandNum.value += 10
}
provide('changeNum', changeNum)
</script>

孙子组件

vue 复制代码
<template>
  <p>爷爷数字:{{ num }}</p>
  <button @click="addFn">调用爷爷方法修改</button>
</template>

<script setup>
import { inject } from 'vue'
const num = inject('grandNum')
const addFn = inject('changeNum')
</script>
1.3 孙子向祖先反向传值

思路:爷爷向外暴露修改自身数据的方法,孙子调用该方法完成传值。

爷爷组件

vue 复制代码
<script setup>
import { ref, provide } from 'vue'
const text = ref('')

const setText = (val) => {
  text.value = val
}
provide('setText', setText)
</script>

孙子组件

vue 复制代码
<script setup>
import { inject } from 'vue'
const setText = inject('setText')

const sendToGrand = () => {
  setText('孙子传给爷爷的数据')
}
</script>
1.4 inject 默认值(防报错)
js 复制代码
// 若祖先未提供对应数据,使用默认空值,避免报错
const msg = inject('grandMsg', '')

2. 祖孙通信:mitt 事件总线

原理

通过全局总线实现 孙子 → 爷爷 主动传值,适合后代主动触发通知的场景。

孙子组件(发送数据)

js 复制代码
import { bus } from '@/utils/bus'
bus.emit('son-to-grand', '孙子消息')

爷爷组件(监听数据)

js 复制代码
import { bus } from '@/utils/bus'
import { onMounted, onUnmounted } from 'vue'

onMounted(() => {
  bus.on('son-to-grand', (data) => {
    console.log('收到孙子数据', data)
  })
})

// 组件销毁必须解绑,防止内存泄漏
onUnmounted(() => {
  bus.off('son-to-grand')
})
优缺点

✅ 适合子孙主动向顶层祖先发送通知、回调

❌ 需手动解绑事件,大型项目易出现事件名冲突

六、核心总结

  1. 组件化核心:复用、拆分、隔离、易维护、高性能

  2. 单文件组件:.vue 文件,结构、逻辑、样式三位一体

  3. 父子通信:props(父传子)、emit(子传父)、defineModel(双向绑定)、ref(父控子)

  4. 祖孙通信:优先 provide/inject (原生简洁),主动通知用mitt

(注:部分内容可能由 AI 生成)

相关推荐
风止何安啊1 小时前
🚦 前端并发请求 “交通管制”:别让你的接口堵成早高峰
前端·javascript·面试
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(二十六):icon 组件封装
前端·vue.js·typescript
李明卫杭州1 小时前
Vue2 & Vue3 中 Router `props` 配置详解:彻底摆脱 `$route` 依赖
前端·vue.js·前端框架
熊猫钓鱼>_>1 小时前
2026 前端的三场底层革命:当 Vue、React Native 和 Three.js 同时去掉中间层
前端·javascript·vue.js·react native·架构·webgl·three.js
名字还没想好☜1 小时前
React useEffect 依赖数组踩坑:闭包陷阱与清理函数
前端·javascript·react.js·react·useeffect
自己记录_理解更深刻2 小时前
Computed 聚合依赖+ 竞态处理
vue.js
柒和远方3 小时前
LeetCode 4. 寻找两个正序数组的中位数 —— 二分划分的艺术
javascript·python·算法
默_笙3 小时前
😢 我被 JS 的 this 整不会了:同一个函数,换个“地区”调用,结果就不一样
javascript
Tom@敲代码3 小时前
js学习笔记-01
javascript·笔记·学习