当前位置: 首页 > article >正文

vue事件总线(原理、优缺点)

目录

  • 一、原理
  • 二、使用方法
  • 三、优缺点
    • 优点
    • 缺点
  • 四、使用注意事项
  • 具体代码
  • 参考:

一、原理

在Vue中,事件总线(Event Bus)是一种可实现任意组件间通信的通信方式。
要实现这个功能必须满足两点要求:
(1)所有组件都能看到它;
(2)可以进行事件的监听;

对于第一个要求,Vue通过内置关系VueComponent.prototype.__proto__ === Vue.prototype,使组件实例对象可以访问到Vue原型上的属性和方法,所以只需把事件总线放在Vue的原型对象上,它就可以被所有组件访问。

至于第二个要求,Vue在原型对象上定义了$on、$emit、$off等方法,用于实现事件监听。基本原理是基于消息订阅发布:调用$on方法时,将函数存在以事件名为key的数组里;当调用$emit时,获取相应数组里的所有函数,并逐个执行。

源码位置:https://github.com/vuejs/vue/blob/main/src/core/instance/events.ts
在这里插入图片描述
在这里插入图片描述
所以事件总线就是一个定义在Vue原型对象上的Vue实例。

new Vue({
  el: '#app',
  render: h => h(App),
  beforeCreate() {
    Vue.prototype.$bus = this;
  }
})

二、使用方法

一个A组件想给B组件发送数据的场景:
A组件:

// A组件想发送数据,则触发事件并传递参数,参数可以是零个到多个
this.$bus.$emit(事件名, 参数);

B组件:

// B组件想接收数据,则在B组件中给$bus绑定自定义事件,事件的回调留在B组件自身
this.$bus.$on(事件名, 回调函数);
// 需要在beforeDestory钩子中解绑事件,避免内存泄露
this.$bus.$off(事件名, 回调函数);

三、优缺点

优点

  1. 任意组件间通信
  2. 组件解耦
    通过使用事件总线,可以将组件之间的直接依赖关系解耦,使组件更加独立和可复用。组件只需要关注自身的功能,而不用关心其他组件的实现细节。

缺点

  1. 只能被动接收数据,不能随时获取状态
    如果需要随时获取状态,可以使用状态管理工具Vuex或者Pinia。
  2. 代码难以调试、数据流向难以追踪
    在大型应用中,事件总线的滥用可能导致组件之间的关系变得混乱,导致追踪代码执行流程和调试变得更加困难。
  3. 潜在的性能问题
    大量的全局事件监听和触发可能导致性能问题,尤其是在频繁触发事件的情况下。

四、使用注意事项

  1. 避免事件命名冲突
    由于事件总线是一个全局的对象,为避免事件命名冲突导致错误触发/解绑事件,建议使用具唯一性的命名空间前缀区分不同的事件。
  2. 避免错误解绑事件
    在解绑事件时,一定要带上事件名和相应的回调函数,否则可能会错误解绑其他组件的事件,影响其他组件的正常运行。
// 仅将回调函数与事件名解绑
this.$bus.$off(事件名, 回调函数);
// 解绑事件名下的所有事件
this.$bus.$off(事件名);
// 解绑所有事件
this.$bus.$off();
  1. 回调函数不能是匿名函数
    匿名函数会导致事件无法正常被解绑
    在这里插入图片描述
  2. 没有解绑事件,可能导致内存泄露
    当组件被销毁时,相关DOM结点已经从DOM树分离出来了,但是还有绑定的事件指向它,导致这些DOM结点无法被垃圾回收,一直在内存里面,就会引发内存泄露。
    所以在组件销毁时,可以在组件的beforeDestroy钩子中使用$off方法解绑事件,以防止内存泄漏。
    没有解绑时:
    在这里插入图片描述

正常解绑时:
在这里插入图片描述

  1. 注意事件的传参
    在发送事件时,可以通过参数传递数据。但请确保传递的数据是简单且不可变的,避免直接传递引用类型的数据,以免造成数据不一致或意外的修改。
    receive(receiveData) {
      this.receiveData = receiveData;
      // 在接收的组件中修改了对象中的值,发送的组件中的值也会改变
      this.receiveData.text = '111';
    },
  1. 慎用全局事件
    全局事件有很大的便利性,但也容易造成不可预测的问题。在使用事件线时,尽量避免滥用全局事件,可以考虑使用更明确的通信方式。
    (1)props和自定义事件应该是父子间通信的首选;
    (2)兄弟节点通信可通过它们的父节点进行;
    (3)隔代组件通信可以使用provide/inject。它可以避免“prop逐级透传”问题,即prop需要通过许多层级的组件传递下去,但这些组件本身可能并不需要那些prop。
    (4)全局共享的数据管理,一般使用Pinia或Vuex等工具。

具体代码

main.js

import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'

Vue.config.productionTip = false
Vue.use(ElementUI)

new Vue({
  el: '#app',
  render: h => h(App),
  beforeCreate() {
    Vue.prototype.iBus = this;
  }
})

App.vue(用v-if就可以触发组件销毁,不一定要用tab)

<template>
  <div>
    <el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="closeTab">
      <el-tab-pane
          v-for="item in editableTabs"
          :key="item.name"
          :label="item.title"
          :name="item.name"
      >
        <send-tab v-if="item.name === 'sendTab'"/>
        <receive-tab v-else/>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>

<script>

import ReceiveTab from "@/components/ReceiveTab.vue";
import SendTab from "@/components/SendTab.vue";

export default {
  name: 'App',
  components: {SendTab, ReceiveTab},
  data() {
    return {
      editableTabsValue: 'sendTab',
      editableTabs: [
        {
          title: '发送页面',
          name: 'sendTab',
        },
        {
          title: '接收页面',
          name: 'receiveTab',
        },
      ],
      tabIndex: 1,
    }
  },
  methods: {
    closeTab(targetName) {
      let tabs = this.editableTabs;
      let activeName = this.editableTabsValue;
      if (activeName === targetName) {
        tabs.forEach((tab, index) => {
          if (tab.name === targetName) {
            let nextTab = tabs[index + 1] || tabs[index - 1];
            if (nextTab) {
              activeName = nextTab.name;
            }
          }
        });
      }
      this.editableTabsValue = activeName;
      this.editableTabs = tabs.filter(tab => tab.name !== targetName);
    }
  }
}
</script>

SendTab.vue

<script>
export default {
  name: "SendTab",
  data() {
    return {
      input: {
        text: '',
      },
    }
  },
  methods: {
    send() {
      console.log('--------------emit', this.iBus._events)
      this.iBus.$emit('bus-demo', this.input, 'data2');
    },
  },
  beforeDestroy() {
    console.log('--------------发送组件的beforeDestroy')
  },
}
</script>

<template>
  <div>
    <el-input style="width: 250px" v-model="input.text"/>
    <el-button type="primary" @click="send">发送数据</el-button>
  </div>
</template>

ReceiveTab.vue

<script>
export default {
  name: "ReceiveTab",
  data() {
    return {
      receiveData: null,
    }
  },
  created() {
    this.iBus.$on('bus-demo', this.receive);
    // this.iBus.$on('anon-func', () => {
    //   console.log('---------------匿名函数')
    // });
    console.log('--------------on', this.iBus._events)
  },
  beforeDestroy() {
    this.iBus.$off('bus-demo', this.receive);
    // this.iBus.$off('anon-func', () => {
    //   console.log('---------------匿名函数')
    // });
    console.log('--------------接收组件的beforeDestroy')
  },
  methods: {
    receive(receiveData, otherData) {
      this.receiveData = receiveData;
      // this.receiveData.text = '111';
      console.log('---------第二个参数', otherData)
    },
  },
}
</script>

<template>
  <span>接收到的数据:{{ receiveData }}</span>
</template>

参考:

  • Vue3迁移指南-事件总线
  • 尚硅谷Vue教程-84/85事件总线
  • 【Vue知识】$on和$emit的实现原理
  • 详解Vue事件总线的原理与应用:EventBus
  • 一个Vue页面的内存泄露分析

http://www.kler.cn/a/522106.html

相关文章:

  • Linux pkill 命令使用详解
  • 元素的显示与隐藏
  • 网络工程师 (7)进程管理
  • LeetCode热题100中 17. 20. 53. 78. 215.
  • 【深度学习】 UNet详解
  • 抖音上线打车服务?抖音要大规模杀入网约车了吗?
  • Kafka 深入服务端 — 时间轮
  • 利用JSON数据类型优化关系型数据库设计
  • C语言字符串详解
  • 外部网关协议BGP考点
  • Vue.js组件开发-实现HTML内容打印
  • 【Elasticsearch】_reindex api请求
  • 鸿蒙仓颉环境配置(仓颉SDK下载,仓颉VsCode开发环境配置,仓颉DevEco开发环境配置)
  • 蓝桥杯真题 - 景区导游 - 题解
  • 新中地GIS开发特训营:引领地理空间技术革命
  • golang通过AutoMigrate方法自动创建table详解
  • 【蓝桥杯】43692.青蛙跳杯子
  • 【深度学习基础】多层感知机 | 实战Kaggle比赛:预测房价
  • 【JavaScript笔记】01- 原型及原型链(面试高频内容)
  • cherry USB 键盘分析
  • 算法题(49):反转链表II
  • Python3 OS模块中的文件/目录方法说明十二
  • DeepSeek R1:中国AI黑马的崛起与挑战
  • AI Agent的安全实践:权限控制与数据保护
  • 1. Java-MarkDown文件创建-工具类
  • 系统思维和升维思维以及它们对项目经理重要性