不只懂 Vue 语法:如何用 event bus 或 mitt 实现跨元件传递资料?

问题回答

所谓跨元件,即是两个元件并无父子关系,并没有被对方包着。如果要互相传递资料,可以使用 mitt(在 Vue 2 是 event bus)、Vuex 或 route props 来处理。

Event bus 和 Mitt 的原理是一样,假设现在有两个元件,A 元件和 B 元件。我想把 A 元件的资料传给 B 元件,步骤就是:

  1. 在 A 元件发出某个事件
  2. 在 B 元件使用 on 来注册监听该事件
  3. 当 B 元件销毁时,使用 off 把该监听移除

接下来这几天,我会为以上每种方法,各自写一篇解说文章。此篇文章会集中讨论 mitt 与 eventbus。

Vue 2 的 event bus

先讲解 Vue 2 的 eventbus 用法,因为懂了 eventbus 自然就会懂得用 mitt。注意,event bus 已经不能在 Vue 3 里使用,因为 Vue 3 已移除 $on$off$once 语法, 而 eventbus 需要使用 $on 来监听 $emit 发出的事件,因此没有 $on 的话,就无法用 event bus 的方法。

稍为重温这些语法:

  • $on:注册监听
  • $off:销毁监听
  • $once:只监听一次
  • $emit:发出事件

步骤一:建立 event bus

首先建立一个 event bus,之後我们会使用此 event bus 来监听事件和触发事件。建立 event bus 的常见做法有两种,择一即可:

  1. 在原本的 Vue 实体里,再建立一个 Vue 实体当作 event bus
  2. 建立一个新的 Vue 实体,当作 event bus

第一种做法,就在 main.js 建立 event bus:

Vue.prototype.$bus = new Vue();

Vue.prototype 的语法是在 Vue 实体新增一个属性。属性前需要有 $ 符号,这是官方提倡的一个写法,用作标示这个属性能够在所有元件里使用,同时避免与任何 data, computed 和 methods 的资料命名产生冲突。

第二种做法,就是直接另外建立新的 Vue 实体,把它当作 event bus。例如我在之後的示范例子中,会建立一个名为 eventBus.js 的档案,并建立新的 Vue 实体。

eventBus.js

import Vue from "vue";
export const EventBus = new Vue();

如果是前者,在任何元件里使用 event bus 时,只需要写 this.$bus 来使用。後者就需要先把 event bus import 进来才可以使用:

import { EventBus } from "@/eventBus";
EventBus.$on(...);

步骤二:使用 $on 建立监听事件,$emit 触发事件

假设现在有以下情景:

有两个同阶层的元件,Button.vueMessage.vue,当我按下 Button.vue 的按钮时,就会把在 Button.vue 的资料,传送给 Message.vueMessage.vue 会负责把资料显示出来。

做法就是:

  • Button.vue 绑上 emit,当用户按按钮,就触发 emit 来发出事件,并把资料一并带出去。
  • Message.vue 监听 Button.vue 所发出的事件,当事件从 Button.vue 发出时,就接收资料,并把它塞到 data,再把它显示出来。

Button.vue:

<template>
  <button type="button" @click="sendMessage">send message</button>
</template>
import { EventBus } from "@/eventBus";

export default {
  data() {
    return {
      message: "Hello Vue!",
    };
  },
  methods: {
    sendMessage() {
      // 发出 click-send-msg 事件,并把 message 当作参数传出去
      EventBus.$emit("click-send-msg", this.message);
    },
  },
};

Message.vue:

<template>
  <p>Button 传来的 Message: {{ msg }}</p>
</template>

<script>
import { EventBus } from "@/eventBus";

export default {
  data() {
    return {
      msg: "",
    };
  },
  mounted() {
    // 监听事件
    EventBus.$on("click-send-msg", (msgdata) => (this.msg = msgdata));
  },
};
</script>

步骤三:使用 $off 移除监听

当元件被销毁时,建议在 beforeDestroy hook 里把此元件用到的监听事件移除,保持网页的效能。以下会再详细解释当中原因。

完整程序码示范

https://codesandbox.io/s/vue-2-event-bus-shi-fan-ewj4i?file=/src/components/Message.vue

为什麽要主动移除监听?

这是因为当元件被销毁,在此元件使用 event bus 所注册的 event bus 监听事件并不会随之而消失。 更可怕的情况是,假设有两个元件同时监听同一个事件,即使其中一个元件被销毁,剩下一个元件,但当该元件侦测到该事件时被触发时,结果是网页会发出两个事件,不是一个。这是因为前一个被销毁的元件仍然残留监听事件。

在此附上此文章提出的此例子。明显示范了如果没有移除监听事件的话,会出现两个问题:

  • 即使元件被销毁,也会残留它的监听。以致在触发事件时,会一并触发残留的监听。
  • 即使该元件被销毁,它的资料仍然会存放在记忆体里。如果资料量很大,就会对记忆体造成负担

建议大家把程序码复制贴上到编辑器,以及使用本地 live server 预览跑一次,一边打开 console 和 memory 去看记忆体来理解作者的意思。

此例子所示范的情况是:

  • 根据 count 的数量来决定渲染多少个 BigTextComponent
  • 你可以按 minus 来减少 count 的数值,也就是销毁已建立的 BigTextComponent
  • 每个 BigTextComponentcreated hook 里,都会产生一笔极长的阵列,模拟资料量很大。同时,此元件会监听由 event 按钮发出的事件,当它发出事件时,就会跑 console.log('QQ iDontKnow')
  • 当按下 event 时,会触发在 BigTextComponent 所监听的事件 ('myEvent')。

如果我们不主动使用 $bus.$off('myEvent', this.eventHandler); 来移除元件的监听,即使按下 minus 按钮来销毁元件,还是会残留这些元件的监听,造成以上提到的两个问题:

  • 按下 event 时,会执行多於一个 console.log('QQ iDontKnow')
  • memory 里仍然存有旧元件的那笔极长的阵列资料

因此,需要主动在元件加入移除监视事件的程序码来解决问题。

Vue 3 的 mitt

Vue 3 的 mitt 跟以上提到 Vue 2 所使用的 event bus 几乎是一样。不同的是,mitt 是一个插件,需要事先载入,就是因为 Vue 3 移除了 $on$off 语法,因此要依赖 mitt 此插件来完成。

做法跟以上的步骤一样,同样是注册监听事件和建立触发事件,在此就不再重复解说,直接分享完整的程序码示范:

https://codesandbox.io/s/vue-3-mitt-shi-fan-ox46v?file=/src/main.js

补充一点,Vue 3 建议使用 beforeUnmount ,而非 beforeDestroy

总结

  • 要实现跨元件传递资料,在 Vue 2 可以使用 event bus,Vue 3 则使用 mitt 插件来完成。
  • 步骤就是在某一元件,使用 $on 注册监听事件,同时在另一个元件使用 $emit 来触发事件。
  • 建议主动在 beforeDestroy (Vue 2)或 beforeUnmount (Vue 3)移除监听事件,保持网页效能。

参考资料

How To Create a Global Event Bus in Vue 2
Vue3 中使用 Event Bus
Vue event bus 介绍
[Vue] Event Bus 是什麽? 怎麽用?
Data Pass Between Components using EventBus in Vue 3


<<:  Day 7:225. Implement Stack using Queues

>>:  Day 0x14 UVa10035 Primary Arithmetic

Day16-Template

再来说样板template,样板只有参数型态不一样其余都相同(包括程序逻辑),样板基本上与写一般的函...

OK集#27-白话文Excel-求状元、榜眼及探花的large

大家跟Large是否有点熟呀~~~就什麽大什麽大的嘛~~~ 喂~~但这里说的是Excel,作者呀~...

[Day 11] -『 GO语言学习笔记』- switch 叙述

以下笔记摘录自『 The Go Workshop 』。 如果遇到需要一大堆if叙述才能处理的状况,就...

Day07:Swift 基础语法-Struct 与 Class 的差异

前言 前面两篇文章学习了 Struct 和 Class, 两者用法相同、功能相似, 都可以用来储存 ...

Day 5 - 使用JWT Token帮Laravel 8.0做Authentication

Introduce 为了API的安全性,本次跟各位介绍透过JWT Token来帮API做身分验证,简...