组件通信:Props 与 Emit

第7课 组件通信:Props 与 Emit

  • 能将页面拆分为独立的组件并组合使用
  • 掌握 Props 实现父组件向子组件传递数据(类型校验、默认值)
  • 掌握 Emit 实现子组件向父组件通信(自定义事件传参)
  • 理解单向数据流与"数据在谁手里,逻辑就在谁那里"的设计原则

1. 组件的基础使用

组件就是一个独立的 .vue 文件,自带 template、script、style 三件套。使用组件需要三步:

步骤 操作 写法
创建 新建 .vue 文件 src/components/AppHeader.vue
注册 在父组件中 import 引入 import AppHeader from './components/AppHeader.vue'
使用 在父组件模板中像标签一样写 <AppHeader />

在 <script setup> 中 import 即自动注册,无需额外代码。


2. 父传子:Props

Props 是父组件传递给子组件的"数据包裹",子组件通过 defineProps 声明自己需要接收哪些数据。

2.1 子组件声明接收

vue 复制代码
<script setup>
// AppHeader.vue
const props = defineProps({
  title: {
    type: String,           // 类型校验
    default: '后台管理系统'  // 默认值(父组件没传时使用)
  },
  userInfo: {
    type: Object,
    default: () => ({ name: '游客', role: '访客' })  // 对象/数组默认值必须用函数返回
  }
})
</script>

声明后,模板中直接使用 props 名称 ,无需 .value:{``{ title }}、{``{ userInfo.name }}。

2.2 父组件传值

vue 复制代码
<!-- 静态传值(不带冒号):直接传字符串 -->
<AppHeader title="静态标题" />

<!-- 动态传值(带冒号):将变量的值传过去 -->
<AppHeader :user-info="userInfo" />

关键 :动态传值必须加冒号 :,否则右边会被当作字符串字面量(如 "userInfo" 这个字符串而不是变量值)。

2.3 命名规则(驼峰 vs 短横线)

  • 子组件声明 时使用 camelCase (驼峰):userInfo
  • 父组件传递 时使用 kebab-case (短横线)::user-info
  • 模板中使用 时使用 camelCase :{``{ userInfo }}
  • Vue 会自动进行转换,单单词无需变化(如 title)。

2.4 单向数据流

Props 是只读的:子组件不能直接修改 接收到的 props,否则控制台会警告(target is readonly)。

如果需要改变,应通过 emit 通知父组件修改,或由父组件重新传入新值。

2.5 类型校验与默认值

  • type:可指定 String、Number、Boolean、Array、Object 等,传错时控制台会亮警告(不影响运行)。
  • default:基本类型直接写值;对象/数组必须写成 函数,每次返回新对象副本,避免多个组件实例共享同一份数据。

3. 子传父:Emit (自定义事件)

子组件可以通过 emit 向父组件"按门铃"------通知父组件发生了什么事,并可携带参数。

3.1 子组件声明与触发

vue 复制代码
<script setup>
// AppMenu.vue
const emit = defineEmits(['select-menu'])  // 声明事件

function handleClick(menu) {
  emit('select-menu', menu)  // 触发事件,并将 menu 对象作为参数传出
}
</script>

3.2 父组件监听与处理

vue 复制代码
<template>
  <AppMenu @select-menu="handleSelectMenu" />
</template>

<script setup>
function handleSelectMenu(menu) {
  // menu 就是子组件传过来的参数
  activeMenu.value = menu.name
}
</script>

事件带参 :emit('事件名', 参数) 的第二个参数即载荷;父组件监听函数直接以参数形式接收。

3.3 事件命名

  • 建议使用动词开头 或短横线连接:logout、select-menu
  • 三处命名必须完全一致 :defineEmits(['...'])、emit('...')、@...="",这是最常见的坑。

4. 组件通信模式总结

方向 机制 写法关键词
父 → 子 Props 子:defineProps;父::属性="数据"
子 → 父 Emit 子:defineEmits + emit('事件');父:@事件="函数"

核心原则 :"数据在谁手里,逻辑就在谁那里" 。

父组件持有数据和业务逻辑,子组件只负责展现 和上报事件。


5. 组件命名与拆分规范

命名规范:

  • 组件文件名:PascalCase + 多单词 (如 AppHeader.vue),避免与原生标签冲突。
  • 自定义事件:动词开头、短横线分隔(如 select-menu)。
  • 应用级组件建议加统一前缀(如 App),方便识别。

何时拆分组件(三个信号):

  1. 同一段代码在多个地方重复出现(如多个页面共用同一个头部)
  2. 单个文件过长,滚动寻代码费劲
  3. 某块区域的数据和逻辑自成一体,与其它部分无关

6. 常见问题排查表

现象 可能原因 解决办法
组件不显示 import路径错误、文件名大小写不对、模板中未使用标签 核对路径、文件名,确认标签已写在模板中
子组件接收不到数据 父组件未传值、动态传值忘加冒号、属性名不一致(驼峰/短横线) 检查 :user-info 与 userInfo 对应,确保已传
控制台类型校验警告 传入的值类型与 defineProps 声明不符 按警告提示修正类型(例如应传对象却传了数字)
子组件修改 props 报警告 违反了单向数据流 改用 emit 通知父组件修改,或由父组件重新传值
点击子组件按钮无反应 事件名三处不一致(defineEmits/emit/@事件) 让这三处的名字保持完全一致
菜单高亮/内容不切换 父组件监听函数未更新响应式数据 确认函数中修改了对应的 ref/reactive 状态

关键代码速查

vue 复制代码
<!-- 父组件:传递数据 + 监听事件 -->
<AppHeader title="后台管理系统" :user-info="userInfo" @logout="handleLogout" />

<!-- 子组件接收 Props 声明 -->
<script setup>
const props = defineProps({
  title: { type: String, default: '默认标题' },
  userInfo: { type: Object, default: () => ({ name: '游客' }) }
})
</script>

<!-- 子组件 Emit 声明与触发 -->
<script setup>
const emit = defineEmits(['logout', 'select-menu'])
emit('select-menu', { name: '用户管理' })  // 带参
</script>
相关推荐
一水鉴天6 分钟前
统一对象拓扑(UOT)设计全链定稿:从三件事分离到三继承 之1 20261010(豆包“查看聊天内容”继续)
前端·css
栖凤17 分钟前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
粥里有勺糖18 分钟前
视野修炼第136期 | 前端小恐龙"Deno"被收购
前端·github·ai编程
修炼的dance22 分钟前
页面塞了13.88 MB字体,导出的PDF只有71 KB
前端
qq_3691736323 分钟前
如何将 AI 生成的 HTML 网页发布成在线链接?不用自己搭建服务器
前端·人工智能·html·效率工具·html 发布
风花一世月31 分钟前
⚡ 我把一座含氢能源园区搬进了浏览器(五类能流实时算守恒,在线直接玩)
前端·人工智能
羲云1 小时前
Claude 越界事件之后:Agent 权限设计为什么不能只靠提示词
前端
行者全栈架构师1 小时前
【鸿蒙心迹】鸿蒙网络请求架构实战——@ohos.net.http 到 Axios 封装、拦截器与统一错误处理(HarmonyOS 7.x)
前端·算法·架构
全栈Agent 小李1 小时前
【无标题】
前端·后端·agent·ai编程·全栈·cursor·mcp
羲云1 小时前
别让 AI 凭记忆做调研:给 WorkBuddy 接上可核验的实时搜索
前端