组件通信: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>
相关推荐
锋行天下2 小时前
DeepSeek写Langgraph就是个大傻逼,一本正经胡说八道
前端·人工智能·后端
jktantan2 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
计算机魔术师3 小时前
Anthropic官方插件被「寄生」,背后是同一类攻击又来了
前端
我命由我123453 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
key_3_feng3 小时前
评估与对齐:Agent 自我进化的护栏
前端·数据库·agent
LaughingZhu4 小时前
Product Hunt 热榜 2026-10-30:Empacer、Scape、Floot in ChatGPT
java·前端·数据库·人工智能·chatgpt·notion
Lank_M4 小时前
抠图结果缩放后边缘发脏,该怪的不是插值
前端
jerrywus4 小时前
Skill、MCP、Workflow、Rules、Memory 有什么区别?拿几次真实开发任务讲清楚
前端·agent·claude
补码缩写4 小时前
canvas 与 sharp 缩放透明图:默认预乘,非预乘得拆通道
前端
静默回滚4 小时前
抠图 PNG 缩小再放大,边缘又糊又白是两件事
前端