
在 vue 3 中,若未在子组件中显式声明 `emits: ['click']`,父组件绑定的 `@click` 会同时接收自定义事件和原生 dom 点击事件(含 `pointerevent` 对象),导致 `btnvalue[object pointerevent]` 的异常拼接。根本原因是事件未声明时,vue 无法区分自定义事件与原生事件冒泡。
这个问题的本质在于 Vue 3 的事件解析机制:当组件未通过 emits 选项声明自定义事件时,所有通过 this.$emit() 触发的同名事件(如 'click')不会被 Vue 视为“组件级事件”,而是与原生 DOM 事件混同处理。此时,父组件上写的 @click="handler" 实际会响应两件事:
- 子组件内部 触发的原生点击事件(携带 PointerEvent 对象);
- 子组件调用 this.$emit('click', value) 发出的自定义事件(携带 btnValue 字符串)。
由于两者事件名相同且未声明,Vue 默认将原生事件对象透传给父组件处理器,于是 value 实际变成了 event 对象,字符串拼接后就出现 btnValue[object PointerEvent] 这类错误输出。
✅ 正确做法是:在 Button.vue 中明确声明 emits: ['click'],让 Vue 知道这是一个受控的自定义事件,从而屏蔽原生 click 的冒泡干扰:
<!-- Button.vue --> <script> export default { name: 'Button', props: { btnValue: String }, emits: ['click'], // ✅ 关键修复:声明自定义 click 事件 methods: { handleClick() { console.log('Emitting value:', this.btnValue); this.$emit('click', this.btnValue); // 仅传递 btnValue } } } </script> <template> <div class="button" @click="handleClick"> <h2 class="text-white m-0">{{ btnValue }}</h2> </div> </template>⚠️ 同时注意以下两点避免连锁问题:
立即学习“前端免费学习笔记(深入)”;
-
移除模板中多余的 this. 绑定:原代码 是错误写法 —— @click 自动传入原生事件对象,而 handleClick 内部已可通过 this.btnValue 访问属性。应改为 @click="handleClick" 或更推荐使用箭头函数 @click="() => $emit('click', btnValue)"(组合式 API 下更清晰);
- 避免事件名冲突:click 是高频原生事件名,建议改用语义化名称(如 'input-value' 或 'append')以提升可读性与健壮性:
<!-- 推荐:Button.vue(组合式 API 风格) --> <script setup> import { defineProps, defineEmits } from 'vue' const props = defineProps({ btnValue: { type: String, required: true } }) const emit = defineEmits(['append']) const handleClick = () => { emit('append', props.btnValue) } </script> <template> <div class="button" @click="handleClick"> <h2 class="text-white m-0">{{ btnValue }}</h2> </div> </template>对应地,ButtonPanel.vue 和 Calculator.vue 中的事件监听也需同步更新为 @append="addToExpression" 和 @append="updateExpression"。
? 总结:Vue 3 的 emits 选项不仅是文档提示,更是运行时事件过滤机制的核心。任何自定义事件都必须显式声明,否则将面临原生事件污染、类型不可控、调试困难等问题。养成 defineEmits([...]) + 语义化事件名的习惯,是构建健壮 Vue 组件的基础实践。










