47

如何在 Vue 3 中创建事件总线?


在 Vue 2 中,它是:

export const bus = new Vue();
bus.$on(...)
bus.$emit(...)

在 Vue 3 中,Vue不再是构造函数,而是Vue.createApp({});返回一个没有$on$emit方法的对象。

4

4 回答 4

111

正如官方文档中所建议的,您可以使用mitt库在组件之间调度事件,假设我们有一个侧边栏,header其中包含一个关闭/打开侧边栏的按钮,我们需要该按钮来切换侧边栏组件内的某些属性:

在 main.js 中导入该库并创建该发射器的实例并定义为全局属性

安装 :

npm install --save mitt

用法 :

import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt';
const emitter = mitt();
const app = createApp(App);
app.config.globalProperties.emitter = emitter;
app.mount('#app');

在标头中发出toggle-sidebar带有一些有效负载的事件:

<template>
  <header>
    <button @click="toggleSidebar"/>toggle</button>
  </header>
</template>
<script >
export default { 
  data() {
    return {
      sidebarOpen: true
    };
  },
  methods: {
    toggleSidebar() {
      this.sidebarOpen = !this.sidebarOpen;
      this.emitter.emit("toggle-sidebar", this.sidebarOpen);
    }
  }
};
</script>

在侧边栏中接收带有有效负载的事件:

<template>
  <aside class="sidebar" :class="{'sidebar--toggled': !isOpen}">
  ....
  </aside>
</template>
<script>
export default {
  name: "sidebar",
  data() {
    return {
      isOpen: true
    };
  },
  mounted() { 
    this.emitter.on("toggle-sidebar", isOpen => {
      this.isOpen = isOpen;
    });
  }
};
</script>

对于那些使用组合 api 的人,他们可以使用emitter如下:

创建一个文件 src/composables/useEmitter.js

import { getCurrentInstance } from 'vue'

export default function useEmitter() {
    const internalInstance = getCurrentInstance(); 
    const emitter = internalInstance.appContext.config.globalProperties.emitter;

    return emitter;
}

从那里你可以useEmitter像你一样使用useRouter

import useEmitter from '@/composables/useEmitter'

export default {
  setup() {
    const emitter = useEmitter()
    ...
  }
  ...
}
于 2020-09-22T23:42:13.013 回答
29

在 Vue.js 的第 3 版上,您可以使用第三方库,也可以使用以发布者-订阅者(PubSub 概念)编程模式编写的功能。

事件.js

//events - a super-basic Javascript (publish subscribe) pattern

class Event{
    constructor(){
        this.events = {};
    }

    on(eventName, fn) {
        this.events[eventName] = this.events[eventName] || [];
        this.events[eventName].push(fn);
    }

    off(eventName, fn) {
        if (this.events[eventName]) {
            for (var i = 0; i < this.events[eventName].length; i++) {
                if (this.events[eventName][i] === fn) {
                    this.events[eventName].splice(i, 1);
                    break;
                }
            };
        }
    }

    trigger(eventName, data) {
        if (this.events[eventName]) {
            this.events[eventName].forEach(function(fn) {
                fn(data);
            });
        }
    }
}

export default new Event();

index.js

import Vue from 'vue';
import $bus from '.../event.js';

const app = Vue.createApp({})
app.config.globalProperties.$bus = $bus;
于 2020-09-24T11:54:22.280 回答
2

EventBus 类文件内容:

class EventBusEvent extends Event {
  public data: any

  constructor({type, data} : {type: string, data: any}) {
    super(type)
    this.data = data
  }
}

class EventBus extends EventTarget {
  private static _instance: EventBus

  public static getInstance() : EventBus {
    if (!this._instance) this._instance = new EventBus()
    return this._instance
  }

  public emit(type : string, data?: any) : void {
    this.dispatchEvent(new EventBusEvent({type, data}))
  }
}

export default EventBus.getInstance()

在项目中使用,发出事件:

import EventBus from '...path to eventbus file with class'
//...bla bla bla... code...
EventBus.emit('event type', {..some data..}')

监听事件:

import EventBus from '...path to eventbus file with class' 
//...bla bla bla... code...
EventBus.addEventListener('event type', (event) => { console.log(event.data) })
于 2021-11-09T14:22:46.717 回答
1

使用 Vue 组合和defineEmit你甚至可以让它变得更简单:

<!-- Parent -->
<script setup>
  import { defineEmit } from 'vue'
  const emit = defineEmit(['selected'])
  const onEmit = (data) => console.log(data)
</script>

<template>
    <btnList
        v-for="x in y"
        :key="x"
        :emit="emit"
        @selected="onEmit"
    />
</template>
<!-- Children (BtnList.vue) -->
<script setup>
  import { defineProps } from 'vue'
  const props = defineProps({
      emit: Function
  })
</script>

<template>
    <button v-for="x in 10" :key="x" @click="props.emit('selected', x)">Click {{ x }}</button>
</template>

我只是和一个孩子一起展示了它,但是你可以通过emit函数传递给其他孩子。

于 2021-05-11T15:38:13.200 回答