发布订阅模式在Vue中的应用

296 阅读2分钟

最近的学习中接触到了在 Vue中的发布-订阅模式,在此,希望做个记录并简要谈谈自己的体会

1. 发布订阅模式主要包含哪些内容

  • 发布函数,发布的时候执行相应的回调
  • 订阅函数,添加订阅者,传入发布时要执行的函数,可能会携额外参数
  • 一个缓存订阅者以及订阅者的回调函数的列表
  • 取消订阅(需要分情况讨论)

为什么会使用发布订阅模式呢? 它的优点在于:

实现时间上的解耦(组件,模块之间的异步通讯); 对象之间的解耦,交由发布订阅的对象管理对象之间的耦合关系.

2. 举例

目标

希望用户如下使用代码:

<g-tabs>
        <g-tabs-nav>
            <g-tabs-item name="tab1"></g-tabs-item>
            <g-tabs-item name="tab2"></g-tabs-item>
        </g-tabs-nav>
        <g-tabs-content>
            <g-tabs-pane name="tab1"></g-tabs-pane>
            <g-tabs-pane name="tab2"></g-tabs-pane>
        </g-tabs-content>
</g-tabs>

思路分析

爷爷组件tabs 有数据 selectedTag, 然后会向head和Body传值,然后,head会向自己的三个孩子传值,body会向自己的三个孩子传值

这里我们可以用到provide和inject

provide 选项允许我们指定我们想要提供给后代组件的数据/方法

然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property.

由于爷爷、爸爸、孙子三者之间的信息传递较为复杂,我们选择用eventbus来减少复杂

用new Vue()做EventBus

如果一个对象可以emit,emit, on, $off就是一个eventBus

Tab.vue
@Component
  export default class Tab extends Vue {
  ...
    eventBus = new Vue();
 
    @Provide('eventbus') eventbus: Vue = this.eventBus;

爷爷provide, 然后儿子、孙子里inject

@Component
  export default class TabHead extends Vue {
    @Inject() eventbus!: Vue;
    created(){
      console.log('爷爷给爸爸的eventBus')
            console.log(this.eventbus)
        }
 
  }
@Component
  export default class TabItem extends Vue {
    @Inject() eventbus!: Vue;
    @Prop(String) name: string | undefined;
 
    created() {
      console.log("爷爷给tab item的eventbus")
      console.log(this.eventBus)
      this.eventbus.$on('update:selected', (name: string) => {
        console.log(name);
      });
    }
  }
  • 到底是谁在触发,谁在监听?

this.$emit 是当前实例在触发

this.eventBus.$emit 是eventBus这个对象本身在触发,注意两者的区别

在vue中,事件并不会冒泡,我们需要关注是在哪个对象上触发的事件

实现切换tag功能

  • 第一步是从外界取值

点击一个tag后,爷爷组件会广而告之所有人,XXX被选中了,然后他的孙子们通过eventBus就能知道了!

然后我们根据传进来的变量,计算出css class, 然后更新样式

    @Provide('eventbus') eventbus: Vue = this.eventBus;
 
    mounted() {
      this.eventbus.$emit('update:selected', this.selected);
    }
TabPanel 和 TabItem
 
created() {
      this.eventbus.$on('update:selected', (name: string) => {
        this.active = name === this.name;
      });
 }
  • 第二步是用点击事件从内部更新

因为vue里事件不会冒泡,所以我们监听点击元素:tab-item, 设置onClick事件

<template>
    <div class="tab-item" @click="xxx" :class="classes">
        <slot></slot>
    </div>
</template>
 
<script lang="ts">
  import Vue from 'vue';
  import {Component, Inject, Prop} from 'vue-property-decorator';
 
  @Component
  export default class TabItem extends Vue {
    @Inject() eventbus!: Vue;
    @Prop(String) name: string | undefined;
    @Prop(Boolean) disabled = false;
    active = false;
 
    get classes() {
      return {
        active: this.active
      };
    }
 
    created() {
      this.eventbus.$on('update:selected', (name: string) => {
        this.active = name === this.name;
      });
    }
 
    xxx() {
      this.eventbus.$emit('update:selected', this.name, this)
    }
  }

每次点击,就往事件中心广播 是哪个Name被点击了