Vue中的自定义事件

一、什么是自定义事件?

自定义事件 是 Vue 提供的一种机制,允许子组件通过 $emit 触发一个事件,父组件通过 v-on(即 @)监听该事件并执行回调。

它实现了 "子 → 父" 的数据传递,是 单向数据流 的重要补充。

核心 API

  • $emit(eventName, ...args):在子组件中触发事件
  • @event="handler":在父组件中监听事件

二、基本使用示例

1. 子组件触发事件

html 复制代码
<!-- ChildButton.vue -->
<template>
  <button @click="handleClick">
    点我触发事件
  </button>
</template>

<script setup>
// Vue 3: 使用 defineEmits 定义可触发的事件
const emit = defineEmits(['click', 'update'])

function handleClick() {
  // 触发 'click' 事件,可携带参数
  emit('click', { id: 1, name: '按钮1' })
  
  // 也可触发多个事件
  emit('update', Date.now())
}
</script>

defineEmits 是 Vue 3 <script setup> 中的宏,用于声明组件可触发的事件。


2. 父组件监听事件

html 复制代码
<!-- ParentComponent.vue -->
<script setup>
import ChildButton from './ChildButton.vue'

// 定义事件处理函数
function onChildClick(data) {
  console.log('子组件点击了:', data)
}

function onChildUpdate(timestamp) {
  console.log('子组件更新时间:', timestamp)
}
</script>

<template>
  <div>
    <h2>父组件</h2>
    <!-- 使用 v-on 监听子组件事件 -->
    <ChildButton 
      @click="onChildClick" 
      @update="onChildUpdate"
    />
  </div>
</template>

✅ 当子组件点击时,父组件的 onChildClick 函数会被调用。

三、Vue 2 vs Vue 3 写法对比

场景 Vue 2 写法 Vue 3 <script setup> 写法
触发事件 this.$emit('event', data) emit('event', data)
定义事件 (可选)emits: ['event'] const emit = defineEmits(['event'])
访问 emit this.$emit 通过 defineEmits 返回

Vue 2 示例

javascript 复制代码
// ChildComponent.vue
export default {
  methods: {
    handleClick() {
      this.$emit('click', 'Hello from child')
    }
  }
}
html 复制代码
<!-- Parent.vue -->
<ChildComponent @click="handleClick" />

✅ Vue 3 的写法更简洁、类型友好。

四、高级用法与最佳实践

1. 事件命名规范

  • 使用 kebab-case (短横线命名):@update-user@item-click
  • 避免使用原生 DOM 事件名(如 clickinput),除非你确实要覆盖

✅ 推荐:@update:modelValue@close-modal


2. v-model 的事件实现原理

v-model 本质上是 :modelValue + @update:modelValue 的语法糖。

子组件(可编辑输入框)
html 复制代码
<!-- CustomInput.vue -->
<script setup>
const emit = defineEmits(['update:modelValue'])
defineProps(['modelValue'])

function onInput(e) {
  emit('update:modelValue', e.target.value)
}
</script>

<template>
  <input 
    :value="modelValue" 
    @input="onInput" 
    type="text"
  />
</template>
父组件使用 v-model
html 复制代码
<script setup>
import CustomInput from './CustomInput.vue'
import { ref } from 'vue'

const inputValue = ref('')
</script>

<template>
  <!-- v-model 自动绑定 modelValue 和 update:modelValue -->
  <CustomInput v-model="inputValue" />
  <p>输入内容:{{ inputValue }}</p>
</template>

✅ 这就是 v-model 的工作原理!


3. 使用 defineEmits 进行类型校验(TypeScript)

html 复制代码
<script setup lang="ts">
// 定义事件类型
const emit = defineEmits<{
  (e: 'add', id: number): void
  (e: 'delete', id: number, reason: string): void
  (e: 'change', value: string): void
}>()

function handleAdd() {
  emit('add', 123) // 类型安全 ✅
}
</script>

✅ TypeScript 下,IDE 会自动提示事件名和参数类型。


4. 事件修饰符

Vue 支持事件修饰符,也可用于自定义事件:

html 复制代码
<!-- .once:只监听一次 -->
<ChildComponent @click.once="handleClick" />

<!-- .stop:阻止事件冒泡(较少用) -->
<ChildComponent @click.stop="handleClick" />

五、常见误区与解决方案

❌ 误区 1:在 <script setup> 中使用 this.$emit

javascript 复制代码
// ❌ 错误!<script setup> 中没有 this
this.$emit('click')

// ✅ 正确:使用 defineEmits
const emit = defineEmits(['click'])
emit('click')

❌ 误区 2:事件名使用 camelCase

html 复制代码
<!-- ❌ 不推荐 -->
<ChildComponent @itemClick="handle" />

<!-- ✅ 推荐 -->
<ChildComponent @item-click="handle" />

HTML 属性不区分大小写,建议统一使用 kebab-case。


❌ 误区 3:子组件直接修改 props

javascript 复制代码
// ❌ 错误!不要这样做
props.modelValue = 'new value'

// ✅ 正确:通过事件通知父组件
emit('update:modelValue', 'new value')

✅ 遵循单向数据流原则。

六、自定义事件 vs $attrs vs ref

通信方式 适用场景 方向 是否推荐
自定义事件 子 → 父 通知 子 → 父 推荐
$attrs 透传属性和事件 父 → 子 ✅ 适合高阶组件
ref 父 → 子 调用方法 父 → 子 ⚠️ 谨慎使用

📌 通信原则

  • 数据流:props 向下,events 向上
  • 避免过度使用 ref 调用子组件方法

七、总结

核心点 说明
作用 实现子组件向父组件通信
API $emit / defineEmits
语法 @event="handler"
Vue 3 defineEmits 更类型安全
v-model 基于 update:modelValue 事件
最佳实践 使用 kebab-case、避免修改 props

📌 一句话总结
自定义事件是 Vue 组件通信的"标准语言",掌握它,你才能构建可维护的组件体系

八、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
葡萄城技术团队3 小时前
提升 Web 端 JavaScript 的可信度:WAICT 体系详解
javascript
yangwan3 小时前
Ubunut 22.04 安装 Docker 24.0.x
前端·后端
等风起8813 小时前
Element Plus实现TreeSelect树形选择在不同父节点下子节点有相同id的双向绑定联动
前端·javascript
摸着石头过河的石头3 小时前
跨域资源共享(CORS)完全指南:从基础概念到实际应用
前端·javascript
小胖霞3 小时前
阿里云域名解析 + Nginx 反向代理 + HTTPS 全流程:从 IP 访问到加密域名的完整配置
前端
xiaohe06013 小时前
🚀 拥抱 create-uni,一行命令轻松集成 Uni ECharts!
vue.js·uni-app·echarts
2301_801252223 小时前
Vue中的指令
前端·javascript·vue.js
烛阴3 小时前
彻底搞懂Lua闭包
前端·lua