在 Vue 中,子组件通过自定义事件向父组件传递消息与数据,是实现子 → 父通信的核心方案。结合上一节的 Props 父传子,二者配合可完成完整的父子组件双向交互,严格遵循 Vue 单向数据流规范。
修改文件 src/components/MyText/src/index.vue,移除冗余导入(defineProps/defineEmits 是编译时宏,无需手动引入),声明实用的自定义事件,保留原有 Props 和输入框功能:
<template>
<label class="my-label" for="text-input">{{ label }}:</label>
<!-- 绑定输入事件,实时向父组件传值 -->
<input
ref="inputRef"
id="text-input"
type="text"
class="my-input-text"
:value="modelValue"
@input="handleInput"
:placeholder="placeholder"
:maxlength="maxLength"
/>
<!-- 新增提交按钮,触发提交事件 -->
<button class="submit-btn" @click="handleSubmit">提交</button>
</template>
<script setup lang="ts">
// 1. 定义 Props(沿用上期规范)
const props = defineProps({
label: { type: String, required: true },
placeholder: { type: String, default: '请输入内容' },
maxLength: { type: Number, default: 20 },
modelValue: { type: String, default: '' }
})
// 2. 定义自定义事件(TS 泛型严格约束,Vue 3 最新标准)
// 声明事件名称 + 回调参数类型
const emit = defineEmits<{
// 输入事件:传递输入框最新值
input: [value: string]
// Vue3 v-model 标准事件:更新模型值
'update:modelValue': [value: string]
// 提交事件:传递标签名 + 输入值
submit: [label: string, value: string]
}>()
// 输入框变化时触发自定义事件
const handleInput = (e: Event) => {
const value = (e.target as HTMLInputElement).value
emit('input', value)
// 触发 v-model 标准事件,同步父组件数据
emit('update:modelValue', value)
}
// 点击按钮时触发提交事件
const handleSubmit = () => {
emit('submit', props.label, props.modelValue)
}
</script>
<style scoped>
.my-label {
margin-right: 8px;
font-size: 14px;
}
.my-input-text {
width: 80%;
padding: 10px;
border-radius: 3px;
border: 1px solid #ccc;
outline: none;
}
.submit-btn {
margin-left: 8px;
padding: 10px 16px;
border-radius: 3px;
border: none;
background-color: #409eff;
color: #fff;
cursor: pointer;
}
</style>在父组件中,使用 @(v-on 简写)监听子组件的自定义事件,接收子组件传递的参数并处理业务逻辑:
<template>
<div class="demo-box">
<h3>组件自定义事件演示</h3>
<!-- 监听子组件的 input / submit 事件 -->
<MyText
label="用户名"
v-model="username"
@input="handleInputChange"
@submit="handleSubmit"
/>
<!-- 展示子组件传递的数据 -->
<p class="result">输入值:{{ username }}</p>
</div>
</template>
<script setup lang="ts">
import { MyText } from './components/MyText'
import { ref } from 'vue'
// 响应式数据
const username = ref('')
// 监听子组件输入事件
const handleInputChange = (value: string) => {
console.log('子组件输入的值:', value)
}
// 监听子组件提交事件
const handleSubmit = (label: string, value: string) => {
console.log(`${label} 提交成功:`, value)
alert(`${label}:${value}`)
}
</script>
<style scoped>
.demo-box {
padding: 20px;
}
.result {
margin-top: 16px;
font-size: 14px;
color: #666;
}
</style>defineEmitsdefineEmits 是 <script setup> 专属的编译时宏,用于声明子组件可触发的所有自定义事件,仅能在组件顶层使用,无需导入。
两种常用写法
TS 泛型写法(推荐,类型安全)
const emit = defineEmits<{
// 输入事件:传递输入框最新值
input: [value: string]
// Vue 3 v-model 标准事件:更新模型值
'update:modelValue': [value: string]
// 提交事件:传递标签名 + 输入值
submit: [label: string, value: string]
}>()数组写法(简单场景)
const emit = defineEmits(['input', 'submit'])通过声明的 emit 函数触发事件,语法:
// 第一个参数:事件名
// 第二个及以后参数:传递给父组件的数据
emit('事件名', 参数1, 参数2)父组件使用 @事件名 监听子组件事件
事件命名遵循小驼峰声明、短横线使用(Vue 自动转换)
子组件:emit('inputEvent')
父组件:@input-event
Props:父 → 子 传数据(只读)
自定义事件:子 → 父 传消息 / 数据
二者结合,实现完整的组件通信,是 Vue 组件化开发的基础。
事件命名规范
自定义事件名推荐使用小驼峰,避免与原生 HTML 事件重名。
禁止修改 Props
子组件不直接修改 Props 数据,通过事件通知父组件修改,保证单向数据流。
参数类型约束
TS 项目中必须为事件参数声明类型,提升代码可维护性和健壮性。
v-model 本质v-model 就是 modelValue Prop + update:modelValue 自定义事件的语法糖。
defineEmits 是 Vue 3 声明自定义事件的标准方式,无需手动导入
子组件用 emit() 触发事件,父组件用 @ 监听事件,实现子 → 父通信
结合 Props 与自定义事件,可完成父子组件全双向交互
TS 泛型声明事件,是企业级开发的最佳实践