组件库开发小记(一)组件构建和注册

515 阅读3分钟

组件库开发小记(一)组件构建和注册

组件编写

这里以两类组件为例,一种我称之为普通组件,一种我称之为动态组件。顾名思义,具体见下。

普通组件

平常我们编写一个Button组件,一般直接在template中使用的,便是此类。

chatbox 为例,这样一个组件的核心代码主要是如下结构:

<template>
  <div
    class="ct-chatbox"
    :class="sent ? 'ct-chatbox__left' : 'ct-chatbox__right'"
  >
    <div class="ct-chatbox__outer">
      <div class="ct-chatbox__avatar" v-if="avatar">
        <img :src="avatar" alt="" />
      </div>
      <div class="ct-chatbox__inner">
        <div>{{text}}</div>
      </div>
    </div>
  </div>
</template>

<script>
import "../../theme/chatbox.less";

export default {
  name: "CtChatbox",
  props: {
    text: {
      required: true,
      type: Array,
    },
    sent: {
      type: Boolean,
      default: false,
    },
    // ...
  },
  data() {
    // ...
  },
  mounted() {
    // ...
  },
};
</script>

和我们平常写页面的组件别无二致,但是需要注意以下两点:

  • 要考虑组件的兼容性、弹性、可定制性(传入多种属性)
  • 将组建的样式文件分离出去,方便后期进行按需加载的打包。

动态组件

动态组件主要指的是不需要写入template中即可生成的组件,比如我们最常用的消息提示框,在业务中往往需要在某些操作前后进行确认与提醒,其作用不言而喻,为了更方便的调用它,于是将其构建为函数式。

这里以toast为例子,其核心代码往往要包含除样式外的两个文件,第一个文件如下:

// main.vue
<template>
  <transition name="fade-vertical" appear>
    <div
      v-if="!closed"
      class="ct-toast-wrapper"
      :class="'ct-toast-' + type"
      :style="{ top: offset + 'px' }"
    >
      <p class="ct-toast-content">{{ content }}</p>
    </div>
  </transition>
</template>

<script>
import "../../theme/toast.less";

export default {
  name: "ct-toast",
  data() {
    return {
      closed: false,
      content: "提示信息",
      type: "success", //弹框的样式 success、warning、error
      offset: 20, //弹框默认的偏移量
      duration: 1000, //弹框消失的时间
      timer: null, //准备一个定时器
      closeFunc: null, //扩充一个功能 弹框消失后触发
    };
  },
  mounted() {
    this.timer = setTimeout(() => {
      if (!this.closed) {
        this.close();
      }
    }, this.duration);
  },
  methods: {
    close() {
      this.closed = true;
      //当弹框消失时会调用this.onClose()该函数方法
      if (typeof this.closeFunc === "function") {
        this.closeFunc();
      }
    },
  },
};
</script>

可以看到,麻雀虽小,五脏俱全。常常需要考虑的过渡效果、回调函数、延迟、位置、颜色等都应该囊括其中。而第二个是最为值得学习的地方,用到了Vue的一些方法,分解来看:

  1. 第一步最为核心:创建组建类,能够动态创建组件的实例,且挂载到DOM对象上。

    const ToastClass = Vue.extend(Toast);
    let instance = new ToastClass({ data });
    instance.$mount();
    document.body.appendChild(instance.$el);
    
  2. 第二步可根据传入的参数offset动态调整其位置,其中需要包括所有实例instances,以便未消失的消息框高度得以动态下滑。

    let offset = data.offset || 20;
    instances.forEach((item) => {
    item.$data.offset += instance.$el.offsetHeight + offset;
    });
    instances.push(instance);
    
  3. 第三步便是一个语法糖了,为其绑定一些便捷的函数:

    ["info", "success", "error", "warning"].forEach((type) => {
    ToastBox[type] = function (data) {
     if (typeof data === "string") {
       data = {
         type: type,
         offset: 20,
         content: data,
       };
     } else {
       data.type = type;
       data.offset = 20;
     }
     //整合data后再次去调用Message()
     return ToastBox(data);
    };
    });
    
  4. 第四步,保证所有消息框消失后实例能对应消失,也即C语言的mallocfree

    ToastBox.close = function (instance) {
    instances = instances.filter((item) => item !== instance);
    };
    

这样构建完成后,就可以通过如下方法去调用它:

this.$toast.info("这是一个弹窗");
this.$toast({
  content: "这是一个弹窗",
  type: "info",
  ended: () => {
    alert("消失后的回调函数");
  },
});

组件注册

在组件编写完成后,于是为组件进行注册,并采取需要合适的方式来将组件组织起来,便于全局引入。

单文件注册

这里的注册重点针对普通组件,这与它们的调用方式有关,因为动态组件本身便是函数,调用函数便可以生效,而普通组件本身需要注入到Vue中去。

同样以chatbox为例,其注册非常简单:

import Chatbox from "./src";

Chatbox.install = function (Vue) {
  Vue.component(Chatbox.name, Chatbox);
};

export default Chatbox;

这里涉及Vue的使用逻辑,我们使用一个组件库一般需要像下面这样:

import CreateUI from "@gypsophlia/create-ui";
import "@gypsophlia/create-ui/lib/theme/index.css";
Vue.use(CreateUI);

其中Vue.use的内部便是需要调用CreateUI.install函数来启动组件库,而如果是一个组件的话,仅仅这一行代码即可搞定:Vue.component(Chatbox.name, Chatbox)

全局注册

全局注册依旧只是编辑一个install函数,在该函数中将所有组件和服务进行注册,如下:

// register components
const install = function (Vue) {
  components.forEach((component) => {
      Vue.component(component.name, component);
  });
  // directive
  Vue.use(Loading.directive);
  // service
  Vue.prototype.$toast = Toast;
  Vue.prototype.$modal = Modal;
};

这里在引入components时有一个小技巧,虽然在打包后会出现BUG,但是并不妨碍介绍给大家。我们可以借助require.context来遍历组件库目录,从而快速导入,不必手动一个个import ... from ...,直接阅读代码吧!

/* The following may not work in bundle */
const requireComponents = require.context("", true, /index.js$/);
requireComponents.keys().forEach((key) => {
  if (key !== "./index.js") components.push(requireComponents(key).default);
});

相关资料