这篇文章将简短而温馨,因为它实际上只是为即将发布的另一篇文章做准备(在 Vue/Nuxt 应用程序中拦截移动设备上的后退按钮)。

问题

事件总线,与发布-订阅模式相关是软件开发中相当基本的概念。如果您还没有听说过,我建议您阅读维基百科条目以了解帖子的其余部分。

简而言之,事件总线允许您解耦系统的各个部分,这些部分以某种方式依赖于系统另一部分中发生的事情(事件)。例如,考虑用户登录应该触发在某些组件中获取额外数据的情况。

有些人可能会争辩说,对于 Vue 的反应性和 VueX,事件总线是不必要的。这在某种程度上是正确的——因为这两种机制大大减少了任何显式发布/订阅发生的需要。但是,在我看来,虽然您可以尝试始终只使用计算属性或监视,但在某些情况下,事件总线可能是一种更简单且众所周知的模式。作为开发人员,拥有各种工具并根据生成最简单、最易读和可维护的代码的方式来选择它们是件好事。

Vue $on/$emit/v-on

Vue 带有一个内置的事件总线/发布-订阅机制。任何 Vue 实例都会公开一些相关的方法,包括:$on$emit

提醒:本地事件

通常,我们使用 $emit 方法和 v-on 指令来进行父子组件之间的通信。

例如,在由带有关闭按钮的对话框 (ComponentPart.vue) 组成的子组件中,我们可以有以下内容:

<v-btn @click="$emit('close')">
    <v-icon>close</v-icon>
</v-btn>

然后在父组件中执行以下操作:

<v-dialog v-model="dialog" >
    <component-part @close="dialog = false"></component-part>
</v-dialog>

请注意,@close只是v-on:close的快捷方式。(你能猜出 v-btn 内部发生了什么,它允许我们写@click吗?)

事件总线插件

事件总线使用相同的机制,除了我们需要获取全局可用的组件实例,而不是使用v-on,我们将使用$on。正如我们在之前的系列文章中介绍的那样,做一些事情对于每个访问者,只做一次,在客户端,我们可以创建一个插件。这将初始化我们的事件总线。

eventBus.client.js

import Vue from 'vue'

const eventBus = new Vue();
//this helps WebStorm with autocompletion, otherwise it's not needed
Vue.prototype.$eventBus = eventBus;

export default ({app}, inject) => {
    inject('eventBus', eventBus);
}

用法示例:

假设在我们的 VueX 商店中,我们与后端进行了通信,该通信在用户登录后启动(此处仅通过登录按钮进行模拟)并检索用户详细信息,例如告诉我们用户是否是管理员。一旦我们知道用户是否是管理员,我们想要获取一些额外的管理员数据以显示在组件中。使用 $eventBus,它看起来像这样:

用户详细信息更改时通知

商店/user.js

export const state = () => ({
  userDetails: {
    admin: false
  },
});

export const mutations = {
  reverseUserDetails(state) {
    state.userDetails = {admin: !state.userDetails.admin};
  }
};
export const actions = {
  async fetchUserDetails({commit}) {
    // normally we'd have an axios call here, it would call our API to get user details
    // here I'm just hardcoding the userDetails to values opposite to what they were
    // every time when you "Login" and fetchUserDetails is called you will switch between admin and non-admin
    commit("reverseUserDetails");

    this.$eventBus.$emit("userDetailsChanged");
  }
};

订阅相应组件中的事件

组件/AdminDataDemo.vue

<template>
  <div>
    <span v-if="isAdmin"></span>
    <span v-else>Current user is not admin</span>
  </div>
</template>

<script>
  import {mapState} from 'vuex';

  export default {
    name: "AdminDataDemo",
    computed: {
      ...mapState({
        isAdmin: state => state.user.userDetails.admin,
        adminData: state => state.admin.adminData
      })
    },
    created() {
      //this listener is not needed in SSR-mode
      if (process.client) {
        console.log("Subscribing to know when userDetails change");
        this.$eventBus.$on("userDetailsChanged", () => {
          console.log("We were notified that user details changed, reacting, admin: " + this.isAdmin);
          if (this.isAdmin) {
            this.$store.dispatch('admin/fetchAdminData')
          } else {
            this.$store.dispatch('admin/removeAdminData')
          }
        });
      }
    },
    beforeDestroy() {
      //make sure to always unsubscribe from events when no longer needed
      console.log("Switching off userDetails listener");
      this.$eventBus.$off("userDetailsChanged");
    }
  }
</script>

管理员数据刷新

export const state = () => ({
  adminData: {}
});

export const mutations = {
  setAdminData(state, value) {
    state.adminData = value
  }
};
export const actions = {
  async fetchAdminData({commit}) {
    // normally we'd have an axios call here, it would call our API to get some data specific to admin.
    // here we're just setting something random
    commit("setAdminData",{someValue: Math.random()});
  },
  async removeAdminData({commit}) {
    // if a user logs out, or stops being an admin, we want to remove existing adminData
    commit("setAdminData", {});
  }
};

有什么好处?

您可能会争辩说 user.js 可以直接分派给 admin.js 并使其直接获取额外数据 - 但这意味着,即使需要它的组件未处于活动状态,您也可能会获取管理数据。此外,您还将获取一般用户详细信息与管理功能相结合。

在这个非常简单的情况下,您还可以监控 user.js 存储状态并在userDetails.admin值更改时做出反应。我希望这个简单的例子展示了如何将它用于更复杂的场景。我将在下一篇文章中展示一个这样的场景(拦截移动设备上的后退按钮)。

完整代码

与往常一样,使用此示例的完整工作项目位于Github- 请注意,它只是我目前使用的项目的一个分支。

其他说明:

  • 在 Nuxt 上下文中,您可以简单地使用this.$root,因为它是共享的根 Vue 实例。然而,我非常喜欢用代码尽可能清楚地传达你的意图,所以我选择创建一个非常简单的插件,并具有一个有意义的名称。

  • 我的示例代码总是有很多 console.log 语句,因此,如果您运行它,您可以在控制台上快速轻松地查看发生了什么。如果在实际应用程序中使用此代码,请删除所有这些以避免过多的噪音,或替换为适当的日志框架(如果您使用它)。

Logo

前往低代码交流专区

更多推荐