Vue2.x创建惰性加载的EventBus

859 阅读2分钟

EventBus是Vue2中较为常用的组件通信方式,解决了多组件之间通信和数据共享的问题

这里举个较为典型的例子

某个业务页面

<template>
  <div>
    <!-- 组件A -->
    <component-a></component-a>
    <!-- 组件B -->
    <component-b></component-b>
  </div>
</template>

<script>
import EventBus from "./eventBus.js";

export default {
  created() {
    EventBus.init(); // 初始化
  },

  beforeDestroy() {
    EventBus.destroy(); // 销毁
  }
};
</script>

EventBus.js

import Vue from "vue";

export default new Vue({
  data() {
    return {
      loading: false,
      listData: null
    };
  },

  methods: {
    // 初始化
    init() {
      this.destroy();
      this.loadData();
    },

    // 销毁
    destroy() {
      this.loading = false;
      this.listData = null;
    },

    // 获取数
    loadData() {
      this.loading = true;

      // 通过接口获取数据
      // ....
      this.listData = [/* 业务数据... */];
    }
  }
});

组件A

<template>
  <div v-if="dataLoading">
    菊花转...
  </div>
  <div v-else>
    <!-- 业务代码 -->
  </div>
</template>

<script>
import EventBus from "./eventBus.js";

export default {
  computed: {
    dataLoading() {
      return EventBus.loading;
    },

    listData() {
      return EventBus.listData;
    }
  }
};
</script>

观察上诉代码,会发现有下面几个问题

  1. 只要业务页面打开,那么EventBus就会加载,且业务页面关闭时,EventBus仍会存在,不会随着业务页面关闭而销毁,这样无疑会占用一些内存。
  2. EventBus的生命周期只会触发一次。
  3. 每次打开和关闭业务页面时都需要对EventBus进行初始化和销毁。
  4. 在一些业务场景下,不需要立即创建EventBus,只会在某些特定条件下才会使用到EventBus。

如何解决?

第一、二、三个问题比较好解决,把EventBus用函数的方式创建,然后业务页面用注入的方式提供给子组件。

eventBus.js

image.png

业务页面

image.png

子组件

image.png

但是这种方案有个很大的缺点,仅能父子组件使用,如果是平级组件这种方案就不行了,而且第四个问题是无法解决的

更好的解决方案

封装一个创建惰性加载的EventBus,利用proxy拦截属性访问

createLazyEventBus.js

import Vue from "vue";

const destroyFunctionName = "destroy";

/**
 * 创建惰性加载EventBus
 * @param {*} config
 * @returns
 */
export default function createLazyEventBus(config) {
  let instance = null;

  // 销毁函数
  function destroy() {
    instance.$destroy();
    instance = null;
  }

  return new Proxy(
    {},
    {
      get() {
        const key = arguments[1];

        if (!instance) {
          instance = new Vue(config);
        }

        if (
          key === destroyFunctionName &&
          !instance.hasOwnProperty(destroyFunctionName)
        ) {
          return destroy;
        }

        return Reflect.get(instance, key);
      }
    }
  );
}

改造下EventBus.js

eventBus.js

import createLazyEventBus from "vue";

export default createLazyEventBus({
  data() {
    return {
      loading: false,
      listData: null
    };
  },

  // 初始化代码可以直接写到生命周期函数里面了,而且是自动调用
  created() {
    this.loadData();
  },

  methods: {
    // 获取数
    loadData() {
      this.loading = true;

      // 通过接口获取数据
      // ....
      this.listData = [
        /* 业务数据... */
      ];
    }
  }
});

业务页面还是最初的方式把EventBus impor到组件内就行了,但是无需再手动调用init了,并且EventBus只有在子组件用到的时候才会被真正初始化! 业务页面

<template>
  <div>
    <!-- 组件A -->
    <component-a></component-a>
    <!-- 组件B -->
    <component-b></component-b>
  </div>
</template>

<script>
import EventBus from "./eventBus.js";

export default {
  created() {
    // EventBus.init(); 不需要手动调用init了
  },

  beforeDestroy() {
    EventBus.destroy(); // 需要手动销毁
  }
};
</script>

子组件

<template>
  <div v-if="dataLoading">
    菊花转...
  </div>
  <div v-else>
    <!-- 业务代码 -->
  </div>
</template>

<script>
import EventBus from "./eventBus.js";

export default {
  computed: {
    dataLoading() {
      return EventBus.loading;
    },

    listData() {
      return EventBus.listData;
    }
  }
};
</script>

总结

Vue3中已经不需要EventBus这种组件通信的模式了,但是其中的一些理念思想需要我们记住,比如利用EventBus设计系统的事件总线。惰性加载这种设计模式在日常工作中不经常使用,但是它对性能优化有很大的帮助,它的实现方式也有很多,比如本文使用的Proxy,还有闭包或者class的get等等,我们需要根据实际情况灵活选择合适的实现方案。

知识点链接

[MDN] Proxy文档 developer.mozilla.org/zh-CN/docs/…