You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue 3表单组件submit事件触发两次的原因及是否为预期行为

Vue3中表单组件submit事件触发两次的原因与处理

问题现象

在Vue3中使用原生表单作为组件的顶级DOM元素时,若组件内监听@submit.prevent.stop并通过$emit('submit')通知父组件,父组件的@submit事件会被触发两次。但将表单用div包裹,或者换用非submit的自定义事件名时,该现象消失。

复现代码

const { createApp } = Vue

const TestForm = {
  template: `
    <form @submit.prevent.stop="onSubmit">
      <input type="text" />
      <input type="submit" value="in component" />
    </form>
  `,
  props: {
    eventName: { type: String }
  },
  methods: {
    onSubmit() {
      this.$emit(this.eventName)
    }
  }
}

createApp({
  template: `
    <div>
      <TestForm event-name="submit" @submit="() => onSubmit('with event name [submit]')" />
      <TestForm event-name="other" @other="() => onSubmit('with event name [other]')" />
      <blockquote>
        <p v-for="(log, i) in logs" :key="i">{{ log }}</p>
      </blockquote>
      <input type="button" value="clear" @click="logs = []" />
    </div>
  `,
  components: { TestForm },
  data: () => ({
    logs: []
  }),
  methods: {
    onSubmit(msg) {
      console.log(msg)
      this.logs.push(msg)
    }
  }
}).mount('#app')

原因分析

这是Vue3的预期行为,核心原因是Vue的原生事件自动绑定机制:
当组件的根元素是原生DOM元素时,Vue会自动将父组件监听的原生DOM事件(如submit)代理绑定到该根元素上。此时你手动$emit('submit')又触发了一次自定义事件,最终导致两次触发:

  • 第一次:Vue自动绑定的根表单元素原生submit事件触发
  • 第二次:组件方法中手动$emit('submit')触发的自定义事件

触发该现象需同时满足两个条件:

  • 表单是组件的顶级DOM元素
  • 手动$emit的事件名与原生DOM事件名重合(此处为submit)

解决方案

1. 换用自定义事件名(推荐)

将emit的事件名改为非原生事件名(如form-submit),避免和原生事件冲突:

// 组件内emit自定义事件名
this.$emit('form-submit')

// 父组件监听对应事件
<TestForm @form-submit="() => onSubmit('custom event')" />

2. 禁用原生事件自动绑定

在组件中添加inheritAttrs: false,阻止Vue自动将父组件的事件/属性绑定到根元素上:

const TestForm = {
  inheritAttrs: false, // 禁用自动绑定
  template: `
    <form @submit.prevent.stop="onSubmit">
      <input type="text" />
      <input type="submit" value="in component" />
    </form>
  `,
  methods: {
    onSubmit() {
      this.$emit('submit')
    }
  }
}

3. 给表单添加非根元素包裹

将表单放在一个容器(如div)内,使组件根元素变为非表单元素,Vue就不会自动绑定submit事件到根元素上:

const TestForm = {
  template: `
    <div> <!-- 添加包裹层 -->
      <form @submit.prevent.stop="onSubmit">
        <input type="text" />
        <input type="submit" value="in component" />
      </form>
    </div>
  `,
  methods: {
    onSubmit() {
      this.$emit('submit')
    }
  }
}

内容的提问来源于stack exchange,提问作者rekam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 17:20:33