Code前端首页关于Code前端联系我们

Vue2里的events到底怎么玩?从基础到实战一次讲透

terry 4小时前 阅读数 5 #Vue
文章标签 Vue2;事件处理

刚接触Vue2的小伙伴,是不是一听到“events”就脑袋发懵?自定义事件是干啥的?怎么在组件里传消息?和浏览器的点击、输入这些事件有啥不一样?别担心,这篇文章把Vue2 events从“是啥”到“怎么用”,再到“实战避坑”全讲明白,哪怕你是刚入门的新手,看完也能上手写组件通信~

Vue2里的events是啥?和DOM事件有啥区别?

先搞清楚核心概念:Vue2的events主要指自定义事件,作用是组件之间传递消息(尤其是子组件给父组件传数据),那它和我们熟悉的DOM事件(比如按钮点击@click、输入框输入@input)有啥不一样?

举个例子:浏览器里的按钮点击(@click)是原生DOM事件,触发者是浏览器(用户点了按钮),作用在DOM元素上,而Vue的自定义事件,触发者是我们写的组件(比如子组件里用this.$emit('xxx')触发),作用是让组件之间能“对话”

简单说:DOM事件是“浏览器和元素”的互动,自定义事件是“组件和组件”的互动。

那自定义事件咋工作的?核心逻辑是“子传父”:子组件通过$emit触发一个自定义事件,把数据“抛”出去;父组件通过@事件名监听这个事件,拿到子组件传来的数据,再做处理。

怎么在组件里定义和触发自定义事件?

分两步走:子组件触发事件 + 父组件监听事件,咱们用一个“子组件点击按钮,给父组件传消息”的例子来理解。

步骤1:子组件里用$emit触发事件

假设子组件叫Child.vue,里面有个按钮,点击后要把“你好,父组件!”传给父组件,代码这样写:

<template>
  <button @click="sendMsg">点我给父组件发消息</button>
</template>
<script>
export default {
  methods: {
    sendMsg() {
      // $emit(事件名, 要传的数据)
      this.$emit('send-message', '你好,父组件!');
    }
  }
}
</script>

这里的关键是this.$emit('事件名', 数据) —— 事件名自己起(比如send-message),数据可以是字符串、对象、数组啥的。

步骤2:父组件里用@事件名监听

父组件叫Parent.vue,要接收子组件的消息,得这么写:

<template>
  <div>
    <!-- 引入子组件,用@事件名绑定回调函数 -->
    <Child @send-message="handleMsg" />
    <p>子组件传来的消息:{{ msg }}</p>
  </div>
</template>
<script>
import Child from './Child.vue'
export default {
  components: { Child },
  data() {
    return {
      msg: ''
    }
  },
  methods: {
    handleMsg(data) {
      // data就是子组件$emit传过来的数据
      this.msg = data;
    }
  }
}
</script>

父组件里,<Child @send-message="handleMsg" /> 表示“监听子组件的send-message事件,触发时执行handleMsg函数”。handleMsg的参数data,就是子组件传过来的“你好,父组件!”。

这样一来,子组件点按钮,父组件就能收到消息并更新页面,组件通信就完成啦~

events能传多个参数吗?怎么处理?

当然能!子组件$emit的时候,可以传多个参数;父组件的回调函数也能对应接收,举个例子:子组件要传“姓名”和“年龄”两个数据。

子组件传多个参数

修改Child.vuesendMsg方法:

methods: {
  sendMsg() {
    const name = '小明';
    const age = 18;
    // 传两个参数:name和age
    this.$emit('send-info', name, age);
  }
}

父组件接收多个参数

父组件的handleMsg函数要对应接收两个参数:

methods: {
  handleMsg(name, age) {
    console.log('姓名:', name, '年龄:', age); // 输出:姓名:小明 年龄:18
  }
}

注意哦:父组件的回调函数参数顺序,要和子组件$emit传参的顺序一致~ 要是传了三个参数,父组件回调也得写三个参数来接收。

事件修饰符在events里咋用?和DOM事件有啥不同?

Vue里的事件修饰符(比如.stop.prevent.once这些),在自定义事件DOM事件里的表现不一样,得仔细区分。

自定义事件能用哪些修饰符?

自定义事件(子传父的那种)不是DOM事件,所以像.stop(阻止冒泡)、.prevent(阻止默认行为)这些对自定义事件无效,但有个修饰符很常用:.once —— 让事件只触发一次。

比如父组件监听事件时加.once

<Child @send-message.once="handleMsg" />

这样不管子组件点多少次按钮,handleMsg只会执行第一次,后面再点就没反应了。

.native修饰符是干啥的?

有时候你会看到<Child @click.native="xxx">这种写法,这是把自定义组件的根元素当成DOM元素,绑定原生DOM事件,比如Child组件的根元素是button,那@click.native就是给这个button绑定原生点击事件,但这和自定义事件没关系,属于“给组件绑原生DOM事件”的技巧~

父组件给子组件传事件?还是子传父?别搞混!

很多新手容易把“父传子”和“子传父”搞混,这里明确一下:

  • 父传子:用props传数据(比如父组件给子组件传标题、列表数据)。
  • 子传父:用自定义事件(events)传消息(比如子组件点击后,告诉父组件“我要修改数据啦”)。

那父组件能给子组件传“事件”吗?比如把父组件的方法通过props传给子组件,让子组件调用?理论上能,但不推荐!因为Vue提倡“单向数据流”(父→子通过props传数据,子→父通过events通知),如果子组件直接调用父组件的方法,容易让数据流向变乱,后期维护麻烦,所以优先用“子组件$emit事件,父组件监听处理”的方式~

多层组件嵌套时,events咋跨层级通信?

比如有个爷爷组件(Grandpa)→ 爸爸组件(Father)→ 儿子组件(Son),现在Son要给Grandpa传消息,总不能让Son→Father→Grandpa层层$emit吧?太麻烦!这时候可以用事件总线(Event Bus)或者Vuex。

方法1:事件总线(Event Bus)

原理是创建一个空的Vue实例,当“中间件”,所有组件都通过这个实例来$emit$on事件,实现跨层级通信。

步骤1:创建事件总线

main.js(或单独的bus.js)里创建:

import Vue from 'vue'
// 创建空Vue实例,作为事件总线
export const bus = new Vue()

步骤2:子组件(Son)触发事件

Son.vue里,导入bus,用bus.$emit发消息:

<template>
  <button @click="sendToGrandpa">给爷爷发消息</button>
</template>
<script>
import { bus } from '../main.js' // 假设bus在main.js里
export default {
  methods: {
    sendToGrandpa() {
      bus.$emit('grandpa-msg', '爷爷好!我是孙子~');
    }
  }
}
</script>

步骤3:爷爷组件(Grandpa)监听事件

Grandpa.vue里,导入bus,用bus.$on监听:

<template>
  <div>
    <p>孙子传来的消息:{{ msg }}</p>
  </div>
</template>
<script>
import { bus } from '../main.js'
export default {
  data() {
    return {
      msg: ''
    }
  },
  created() {
    // 监听事件
    bus.$on('grandpa-msg', (data) => {
      this.msg = data;
    })
  },
  beforeDestroy() {
    // 销毁事件,防止内存泄漏
    bus.$off('grandpa-msg');
  }
}
</script>

这样,不管多少层嵌套,只要通过bus.$emitbus.$on,就能跨组件通信~ 但要注意在组件销毁前用$off销毁事件,否则重复创建组件时会多次触发,导致 bug。

方法2:Vuex(更适合复杂场景)

如果项目里已经用了Vuex,跨组件通信更推荐用Vuex的statemutation,比如Son组件提交mutationGrandpa组件通过计算属性获取state里的数据,不过Vuex更适合“多组件共享数据”的场景,简单跨层级用事件总线更轻便~

自定义事件和生命周期有啥关系?啥时候绑定/销毁?

如果是用模板里@事件名的方式(比如父组件<Child @xxx="xxx" />),Vue会自动帮我们处理事件的绑定和销毁,不用操心,但如果是用事件总线或者在JS里手动$on,就得结合生命周期钩子来管理。

比如用事件总线时:

  • created钩子:组件创建后,用bus.$on监听事件(因为created时组件已经能访问this,且DOM还没渲染,适合绑定事件)。
  • beforeDestroy钩子:组件销毁前,用bus.$off销毁事件(否则事件还在,下次创建组件时会重复监听,导致多次触发)。

看个例子(爷爷组件用事件总线时的生命周期处理):

export default {
  data() { ... },
  created() {
    // 绑定事件
    bus.$on('grandpa-msg', this.handleMsg);
  },
  methods: {
    handleMsg(data) {
      this.msg = data;
    }
  },
  beforeDestroy() {
    // 销毁事件
    bus.$off('grandpa-msg', this.handleMsg);
  }
}

这样能保证事件在组件可用时监听,销毁时清除,避免内存泄漏和重复触发~

实战中常见的events错误,怎么避坑?

新手用自定义事件时,很容易踩这些坑,提前避坑少熬夜!

坑1:事件名大小写不统一

Vue2里,自定义事件名推荐用全小写kebab-case(短横线分隔),因为HTML属性不区分大小写,模板里的@事件名是全小写的。

比如子组件$emit('sendMessage')(驼峰),父组件模板里写@send-message才能监听到(因为HTML里@sendMessage会被当成@sendmessage,和子组件的sendMessage不匹配),所以事件名统一用全小写或kebab-case,比如send-messagesendmsg,避免大小写坑!

坑2:父组件忘记绑定事件,子组件$emit没反应

子组件都$emit了,父组件页面没变化?先检查父组件有没有写@事件名,比如子组件$emit('send-msg'),父组件得写<Child @send-msg="xxx" />,没写的话肯定收不到~

坑3:参数传递/接收错误

子组件传了多个参数,父组件回调只写了一个参数,结果拿到的是第一个参数,后面的丢了,比如子组件$emit('send', a, b),父组件handle(data) { ... },这时候dataab没收到,所以传参和接收的参数数量、顺序要一致

坑4:事件总线没销毁,重复触发

用事件总线时,组件销毁前没调用bus.$off,下次再创建组件时,旧的事件还在,导致一次操作触发多次回调,解决方法:beforeDestroy钩子用$off销毁事件

Vue2的events和Vue3有啥区别?学了Vue2对Vue3有帮助吗?

Vue3对自定义事件做了优化,核心逻辑还是“子传父用emit”,但有这些变化:

  • Vue3需要在组件的emits选项里显式声明自定义事件(比如emits: ['send-msg']),让代码更清晰,还能做类型校验。
  • Vue3的$emit是从setup里的上下文获取(ctx.emit),而不是this.$emit

核心思想没变:子组件触发事件,父组件监听,所以学懂Vue2的events,理解组件通信的逻辑,转到Vue3只是语法细节调整,上手会很快~

实战案例:用events做一个“点赞组件”

光说不练假把式,咱们做个小案例:子组件是点赞按钮,点击后给父组件传点赞数,父组件显示点赞结果。

子组件(LikeButton.vue

<template>
  <button @click="handleLike">
    {{ isLiked ? '取消点赞' : '点赞' }}
  </button>
</template>
<script>
export default {
  data() {
    return {
      isLiked: false,
      likeCount: 0
    }
  },
  methods: {
    handleLike() {
      this.isLiked = !this.isLiked;
      this.likeCount = this.isLiked ? this.likeCount + 1 : this.likeCount - 1;
      // 触发自定义事件,把点赞数传给父组件
      this.$emit('like-change', this.likeCount);
    }
  }
}
</script>

父组件(Post.vue

<template>
  <div class="post">
    <h3>我的动态</h3>
    <p>点赞数:{{ totalLikes }}</p>
    <LikeButton @like-change="updateLikes" />
  </div>
</template>
<script>
import LikeButton from './LikeButton.vue'
export default {
  components: { LikeButton },
  data() {
    return {
      totalLikes: 0
    }
  },
  methods: {
    updateLikes(count) {
      this.totalLikes = count;
    }
  }
}
</script>
<style scoped>
.post {
  border: 1px solid #eee;
  padding: 20px;
  margin: 10px;
}
button {
  padding: 6px 12px;
  cursor: pointer;
}
</style>

运行后,点击“点赞”按钮,子组件的likeCount变化,通过$emit('like-change')传给父组件,父组件更新totalLikes并显示,这个案例里,events负责子→父的通信,props(如果有的话)负责父→子,分工明确~

看完这些,是不是觉得Vue2的events没那么难了?自定义事件是组件通信的关键工具,核心是“子$emit发消息,父@事件名收消息”,记住事件名规范、参数传递、跨层级通信的技巧,再避开常见的大小写、销毁事件这些坑,你就能轻松用events搞定组件间的互动啦~ 要是还有疑问,评论区随时问,咱们一起折腾代码~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门