Ehan Van的博客
首页
前端笔记
首页
前端笔记
  • 前端笔记

    • v-on的使用
    • 编写一个Vue递归组件
    • 非vue文件中申明的ref如何使用
    • Vue3如何在hook中执行异步函数并同步返回响应式数据
    • 如何终止forEach循环
    • 递归创建下拉树数据
    • 编写一个Vue Web Components
    • js数组toString的使用
    • vueconfig配置多page遇到的坑
    • js事件循环机制
    • React列表渲染中图片闪烁问题
    • 开发中未配置Referrer遇到的坑
    • 前端实现文件分片上传
    • 防抖函数
    • 编写一个vue插件
    • 发布订阅模式

问题

在调用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" />
  1. 方案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 更适合传数据,事件适合子向父通信)。**
  1. 方案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 接收方法,但这并非官方推荐的事件通信方式。
Next
编写一个Vue递归组件