组件通信: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:可指定 StringNumberBooleanArrayObject 等,传错时控制台会亮警告(不影响运行)。
  • 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 事件命名

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

4. 组件通信模式总结

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

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

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


5. 组件命名与拆分规范

命名规范

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

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

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

6. 常见问题排查表

现象 可能原因 解决办法
组件不显示 import路径错误、文件名大小写不对、模板中未使用标签 核对路径、文件名,确认标签已写在模板中
子组件接收不到数据 父组件未传值、动态传值忘加冒号、属性名不一致(驼峰/短横线) 检查 :user-infouserInfo 对应,确保已传
控制台类型校验警告 传入的值类型与 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>
相关推荐
东方老白1 小时前
iOS 面试题
前端
hunterandroid1 小时前
Compose LazyColumn 性能优化:从卡顿到流畅的大列表治理
前端
喵个咪1 小时前
GoWind Shop 架构剖析:一个 REST 请求穿越三服务 BFF 的七环链路
vue.js·后端·go
喵个咪1 小时前
GoWind Shop 安全设计:JWT 鉴权、Ent 行级隔离、防篡改审计与四个真实漏洞修复
vue.js·后端·go
喵个咪1 小时前
GoWind Shop:一个 proto 文件如何生成后端、前端、文档、校验六路代码
vue.js·后端·go
喵个咪1 小时前
GoWind Shop 出海实战:多语言商品内容怎么存、怎么录、怎么按 locale 取
vue.js·后端·go
sakiko_2 小时前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift
海带紫菜菠萝汤2 小时前
MSE (Media Source Extensions) 实战:流媒体分块加载与自适应码率
前端·javascript·音视频
@小匠2 小时前
Spring Boot Nacos绑定 Map 时中文 key 导致启动失败:一次从复现到源码的排查实录
java·开发语言·前端