使用v-on=”{}”的方式进行组件事件绑定$listeners却是个空对象

问题

在调用SearchForm组件时,使用v-on={}的方式绑定事件,在SearchForm发现$listeners为空对象

<SearchForm v-on="{ search: handleSearch, reset: handleReset  }" />

核心原因

Vue2 中这种对象形式的 v-on 绑定,默认不会把事件挂载到$listeners上,而是会被当作组件的 props / 属性处理,这是 Vue2 和 Vue3 在 v-on 语法上的关键差异,Vue3 已统一该行为。

解决方法

  1. 方案1: 改用 Vue2 标准的多事件分开绑定(最推荐)

    <SearchForm @search="handleSearch" @reset="handleReset" />
  2. 方案2:SearchForm子组件通过 props 接收对象形式的绑定(适配你原来的父组件写法)

    如果想保留父组件v-on="{ ... }"的写法,子组件需要把这些「伪事件」当作props 属性接收,再手动调用

    <template>
    <div>
        <button @click="handleClickSearch">搜索</button>
        <button @click="handleClickReset">重置</button>
    </div>
    </template>
    <script>
    export default {
    // 接收父组件v-on对象中的方法作为props
    props: {
    search: {
      type: Function,
      required: true
    },
    reset: {
      type: Function,
      required: true
    }
    },
    methods: {
    handleClickSearch() {
      // 调用父组件传递的search方法,可传参
      this.search({ keyword: 'test' });
    },
    handleClickReset() {
      // 调用父组件传递的reset方法
      this.reset();
    }
    }
    };
    </script>

    注意:这种方式本质是传方法 props,并非 Vue 的「事件通信」,Vue 官方不推荐频繁这么做(props 更适合传数据,事件适合子向父通信)。

  3. 方案3:手动把$attrs中的方法挂载到listeners(进阶技巧,不推荐新手)

    因为对象形式的 v-on 绑定会被放到$attrs中(Vue2 中,未被 props 接收的属性都会存在$attrs),所以可在子组件的created钩子中,手动把$attrs中的方法合并到$listeners,实现兼容:

    <!-- 子组件 SearchForm.vue -->
    <script>
    export default {
        created() {
            // 把$attrs中的方法(父组件v-on对象绑定的)合并到$listeners
            Object.assign(this.$listeners, this.$attrs);
            // 此时打印$listeners就能拿到search和reset了
            console.log(this.$listeners); // { search: fn, reset: fn }
        }
    };
    </script>

    缺点:这种方式是「hack 技巧」,破坏了 Vue2 的原生设计,可读性差,后期维护容易出问题,非特殊场景

补充: Vue2 中 $listeners 的核心特性

  1. $listeners仅收集通过@事件名直接绑定的原生 / 自定义事件,不包含对象形式 v-on、v-bind 绑定的方法 / 属性;
  2. $listeners是只读对象,Vue2 中不能直接修改(如this.$listeners.search = fn无效),需通过Object.assign合并(方案 3 的原理);
  3. 可通过v-on="$listeners"将父组件的所有事件监听透传给子组件的内部元素(比如子组件中的 input、button),实现事件透传;
  4. Vue2.4 + 新增inheritAttrs: false,可阻止父组件的非 props 属性被渲染到子组件的根元素上,但不影响$attrs和$listeners的收集。

总结

  1. 核心原因:Vue2 中对象形式的v-on="{ ... }"不会挂载到$listeners,而是被当作组件属性放到$attrs中(Vue3 已修复该差异,对象形式 v-on 也会被收集到 emits/listeners);
  2. 最优解决方案:改用 Vue2 标准的@search="handleSearch" @reset="handleReset"分开绑定事件,符合 Vue2 的事件设计,子组件$listeners能正常收集;
  3. 临时适配方案:若想保留父组件写法,可让子组件通过props 接收方法,但这并非官方推荐的事件通信方式。