Vue3 零基础每日笔记(026):emit 子传父——defineEmits 与事件校验

一、前言

props 让数据"往下流",但子组件无权改它。那子组件想改数据怎么办?上报事件,让父组件自己改------这就是 emit(子传父)。它是"单向数据流"的配套通道:数据下行、事件上行,形成闭环。本篇讲透 defineEmits 用法与事件校验,末尾顺手揭开 v-model 的身世。

二、基础用法:defineEmits + $emit 接收

完整闭环三步:子组件声明事件 → 子组件触发事件 → 父组件监听并处理:

vue 复制代码
<!-- src/components/CountButton.vue(子组件) -->
<template>
  <button @click="handleClick">子组件点我 +10</button>
</template>

<script setup>
// 1. 声明要"上报"的事件(像 defineProps 一样显式声明)
const emit = defineEmits(['add-count'])

function handleClick() {
  // 2. 触发事件,可携带数据(载荷)
  emit('add-count', 10)
}
</script>
vue 复制代码
<!-- App.vue(父组件) -->
<template>
  <p>总数:{{ total }}</p>
  <!-- 3. 监听子组件事件,拿到载荷去改自己的数据 -->
  <CountButton @add-count="total += $event" />
</template>

<script setup>
import { ref } from 'vue'
import CountButton from './components/CountButton.vue'

const total = ref(0)
</script>

数据流走一遍:点按钮 → 子组件 emit('add-count', 10) → 父组件 total += 10 → props 流回子组件更新视图。数据改动永远发生在父组件,子组件只负责"喊一嗓子"。

三个细节:

  1. 事件名推荐 kebab-case (如 add-count):事件名不会自动做大小写转换,emit('addCount')@add-count 会失灵------统一 kebab-case 最稳
  2. 载荷可以有多个:emit('change', val, index),监听处 @change="(val, i) => ..." 接收
  3. 模板里可以直接用 $emit(上面展示型场景),逻辑多就接进方法里

三、defineEmits 的校验写法

和 props 的 validator 类似,事件也能声明"载荷长什么样":

js 复制代码
const emit = defineEmits({
  // 有校验:返回 true 通过,false 控制台告警
  'add-count': (num) => typeof num === 'number' && num > 0,

  // 无载荷事件:校验写 null,表示"不校验但事件存在"
  reset: null
})

// 合法:通过
emit('add-count', 10)

// 非法:控制台警告 "[Vue warn]: Invalid event arguments"
emit('add-count', '十')

事件校验同样是开发期提示,适合多人协作时约束"这个事件该传什么"。TypeScript 版(defineEmits 泛型)在 042 篇细讲。

四、一对组合拳:props 下行 + emit 上行

真实组件几乎都是双向配合,写个完整的"可增减计数器":

vue 复制代码
<!-- src/components/Stepper.vue -->
<template>
  <div class="stepper">
    <button @click="emit('change', -1)">-</button>
    <span>{{ model }}</span>
    <button @click="emit('change', 1)">+</button>
  </div>
</template>

<script setup>
defineProps({
  model: { type: Number, required: true }
})
const emit = defineEmits(['change'])
</script>
vue 复制代码
<!-- App.vue -->
<template>
  <p>购买数量(最小 1,最大 5):</p>
  <!-- 上下限校验逻辑在父组件------数据规则跟着数据走 -->
  <Stepper
    :model="count"
    @change="(d) => count = Math.min(5, Math.max(1, count + d))"
  />
</template>

<script setup>
import { ref } from 'vue'
import Stepper from './components/Stepper.vue'

const count = ref(1)
</script>

注意职责划分:子组件只知道"有人点了我,增量是 ±1",完全不知道上下限存在;规则集中在数据的主人(父组件)身上。业务规则散落到子组件里,是组件库越用越乱的开端。

【截图位置:点击加减、数量被限制在 1-5 的运行效果】

五、v-model 的身世之谜(预告 027)

回想 019 篇:v-model = :value + @input 语法糖。把同样的思路放到自定义组件上:

vue 复制代码
<template>
  <!-- 你写的 -->
  <MyInput v-model="msg" />

  <!-- Vue 编译器实际生成的 -->
  <MyInput :model-value="msg" @update:model-value="(v) => msg = v" />
</template>

看出来了吗?组件的 v-model 就是"props 下行 + emit 上行"的语法糖 :父组件传 modelValue prop,子组件 emit update:modelValue 事件,父组件收到就回写自己的数据。单向数据流一点没破坏------"改数据"的仍然是父组件,只是语法糖把这套样板代码藏起来了。

下一篇 027 就专门讲:怎么让自己的组件也支持 v-model。

六、踩坑记录

  1. 事件名大小写失灵emit('addCount') 搭配 @add-count 收不到------事件名不自动转换,要么全 kebab,要么两边一模一样
  2. 声明了却没触发 / 触发了没声明:defineEmits 数组里没写的事件,触发会有警告------新增事件记得同步声明
  3. 在 emit 回调里改 props@change="count = $event" 是改父组件自己的数据 ✅;但 @change="model = $event"(model 是 props)❌ 又回到改 props 的老坑
  4. 载荷对象忘了浅拷贝emit('save', form) 直接把 reactive 对象递出去,父组件一改子组件跟着变------双向数据流渗透,必要时 { ...form }
  5. 事件命名动词不带宾语emit('change') 还行,emit('do') 这种含糊事件名三个月后没人知道它干嘛------命名带业务含义(submit-orderremove-item

七、今日小结

  • emit 三步:子声明 defineEmits → 子触发 emit('事件', 载荷) → 父监听 @事件处理
  • 事件名统一 kebab-case;校验用对象写法,无载荷写 null
  • 标配组件形态:props 下行供数据,emit 上行报事件,规则集中在父组件
  • 组件的 v-model = props(modelValue) + emit(update:modelValue) 的语法糖

下篇预告

既然 v-model 的原理就是 props + emit,那自己的组件怎么"享受"这个语法糖?下一篇 027 组件上的 v-model------modelValue、update:modelValue、defineModel 一个不落。

相关推荐
风骏时光牛马1 小时前
6个高频实用技术脚本案例,覆盖运维与办公自动化(可直接复用)
前端
ssshooter1 小时前
Node.js 天生就是异步的,为什么还要消息队列?
javascript·后端·面试
爱吃红星柚1 小时前
【学习】Elpis 抽离与发布 npm 包
前端·前端框架·node.js
RuoyiOffice1 小时前
SpringBoot3+Vue3 库存盘点:账面和实盘对不上,差异单怎么走审批再调账
spring boot·vue3·erp·spring boot 3·库存盘点·盘盈盘亏
PedroQue992 小时前
0.5.0重磅发布:动画插件+H5体验全面优化
前端·uni-app
金虹桥程序员2 小时前
【动画】 📚 CSS 动画与 GPU 合成层优化全链路指南
前端·面试
程序员老赵2 小时前
Docker 部署填鸭表单完整教程:搭建私有化问卷与表单收集平台
前端·docker·开源
无限压榨切图仔2 小时前
向量库能搜到内容,为什么还不算学会 RAG
前端·agent
mmsx2 小时前
MapLibre 实战 11|用户说"我的地块丢了":一个 sealed class 图层模型,和四个让我重构三版的坑
android·前端·开源