【问题标题】:Vue.js 3 Event BusVue.js 3 事件总线
【发布时间】:2020-12-07 19:51:50
【问题描述】:

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


在 Vue 2 中,它是:

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

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

【问题讨论】:

  • RFC 建议改用第三方库:github.com/vuejs/rfcs/blob/master/active-rfcs/…
  • 你能扩展一下你需要什么功能吗?在某些情况下,即使是 ref 也可以。
  • 为所有组件对象共享一个以在其上发出和侦听事件。 (这就是事件总线的用途。)

标签: javascript vue.js vue-component vuejs3 vue-composition-api


【解决方案1】:

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

在 main.js 中导入该库并创建该发射器的实例并定义为 global property

安装:

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>

对于那些使用composition 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()
    ...
  }
  ...
}

【讨论】:

    【解决方案2】:

    在 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;
    

    【讨论】:

    • 我将 on 更改为 $onoff 更改为 $offtrigger 更改为 $emit 以更好地匹配我的 Vue 2 总线中的方法
    【解决方案3】:

    借助 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 函数传递给其他孩子。

    【讨论】:

    • 只适用于子父方式...
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2016-10-30
      • 1970-01-01
      • 1970-01-01
      • 2018-04-29
      • 1970-01-01
      • 1970-01-01
      • 2017-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多