Vue.js 3事件总线

relj7zay  于 2022-11-17  发布在  Vue.js
关注(0)|答案(6)|浏览(217)

如何在Vue 3中创建事件总线?
在Vue 2中,它是:
第一个
在Vue 3中,Vue不再是一个构造函数,并且Vue.createApp({});返回一个没有$on$emit方法的对象。

rekjcdws

rekjcdws1#

正如官方文档中所建议的,您可以使用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');

in header发出带有某些有效负载的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;
}

从这里开始,您可以像使用useRouter一样使用useEmitter

import useEmitter from '@/composables/useEmitter'

export default {
  setup() {
    const emitter = useEmitter()
    ...
  }
  ...
}

使用合成API

您还可以利用新的组合API并定义可组合的事件总线:

  • 事件总线.js*
import { ref } from "vue";
const bus = ref(new Map());

export default function useEventsBus(){

    function emit(event, ...args) {
        bus.value.set(event, args);
    }

    return {
        emit,
        bus
    }
}

在组分A中:

import useEventsBus from './eventBus';
...
//in script setup or inside the setup hook
const {emit}=useEventsBus()
...
 emit('sidebarCollapsed',val)

组分B:

const { bus } = useEventsBus()

watch(()=>bus.value.get('sidebarCollapsed'), (val) => {
  // destruct the parameters
    const [sidebarCollapsedBus] = val ?? []
    sidebarCollapsed.value = sidebarCollapsedBus
})
k5hmc34c

k5hmc34c2#

在Vue.js版本3中,您可以使用第三方库,也可以使用以发布者-订阅者(PubSub概念)编程模式编写的功能。
event.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;
aiazj4mn

aiazj4mn3#

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) })
ogsagwnx

ogsagwnx4#

我已经修改了another answer,使其具有与Vue示例等效的接口,这样该实用程序就可以作为一个直接的替代品工作,而不需要更改使用的代码。
此版本还支持$off方法,该方法的第一个参数是事件名称的 array。它还避免了$off方法中的一个问题,即取消注册多个事件侦听器实际上会删除一个错误的侦听器,这是因为在正向迭代数组的同时还删除了其中的项目。
event-bus.js

// @ts-check

/**
 * Replacement for the Vue 2-based EventBus.
 *
 * @template EventName
 */
class Bus {
  constructor() {
    /**
     * @type {Map<EventName, Array<{ callback: Function, once: boolean }>>}
     */
    this.eventListeners = new Map()
  }

  /**
   * @param {EventName} eventName
   * @param {Function} callback
   * @param {boolean} [once]
   * @private
   */
  registerEventListener(eventName, callback, once = false) {
    if (!this.eventListeners.has(eventName)) {
      this.eventListeners.set(eventName, [])
    }

    const eventListeners = this.eventListeners.get(eventName)
    eventListeners.push({ callback, once })
  }

  /**
   * See: https://v2.vuejs.org/v2/api/#vm-on
   *
   * @param {EventName} eventName
   * @param {Function} callback
   */
  $on(eventName, callback) {
    this.registerEventListener(eventName, callback)
  }

  /**
   * See: https://v2.vuejs.org/v2/api/#vm-once
   *
   * @param {EventName} eventName
   * @param {Function} callback
   */
  $once(eventName, callback) {
    const once = true
    this.registerEventListener(eventName, callback, once)
  }

  /**
   * Removes all event listeners for the given event name or names.
   *
   * When provided with a callback function, removes only event listeners matching the provided function.
   *
   * See: https://v2.vuejs.org/v2/api/#vm-off
   *
   * @param {EventName | EventName[]} eventNameOrNames
   * @param {Function} [callback]
   */
  $off(eventNameOrNames, callback = undefined) {
    const eventNames = Array.isArray(eventNameOrNames) ? eventNameOrNames : [eventNameOrNames]

    for (const eventName of eventNames) {
      const eventListeners = this.eventListeners.get(eventName)

      if (eventListeners === undefined) {
        continue
      }

      if (typeof callback === 'function') {
        for (let i = eventListeners.length - 1; i >= 0; i--) {
          if (eventListeners[i].callback === callback) {
            eventListeners.splice(i, 1)
          }
        }
      } else {
        this.eventListeners.delete(eventName)
      }
    }
  }

  /**
   * See: https://v2.vuejs.org/v2/api/#vm-emit
   *
   * @param {EventName} eventName
   * @param {any} args
   */
  $emit(eventName, ...args) {
    if (!this.eventListeners.has(eventName)) {
      return
    }

    const eventListeners = this.eventListeners.get(eventName)
    const eventListenerIndexesToDelete = []
    for (const [eventListenerIndex, eventListener] of eventListeners.entries()) {
      eventListener.callback(...args)

      if (eventListener.once) {
        eventListenerIndexesToDelete.push(eventListenerIndex)
      }
    }

    for (let i = eventListenerIndexesToDelete.length - 1; i >= 0; i--) {
      eventListeners.splice(eventListenerIndexesToDelete[i], 1)
    }
  }
}

const EventBus = new Bus()

export default EventBus

old-event-bus.js

import Vue from 'vue'

const EventBus = new Vue()

export default EventBus

example.js

// import EventBus from './old-event-bus.js'
import EventBus from './event-bus.js'
oogrdqng

oogrdqng5#

我只想在这里提到,您也可以使用VueUse定义的useEventBus
下面是一个使用注入键的TypeScript示例。
第一个

oxosxuxt

oxosxuxt6#

借助Vue合成和defineEmit,您甚至可以使其变得更简单:
第一个
我只是用一个孩子演示了它,但是你可以把emit函数传递给其他孩子。

相关问题