组件库开发小记(一)组件构建和注册
组件编写
这里以两类组件为例,一种我称之为普通组件,一种我称之为动态组件。顾名思义,具体见下。
普通组件
平常我们编写一个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的一些方法,分解来看:
-
第一步最为核心:创建组建类,能够动态创建组件的实例,且挂载到DOM对象上。
const ToastClass = Vue.extend(Toast); let instance = new ToastClass({ data }); instance.$mount(); document.body.appendChild(instance.$el); -
第二步可根据传入的参数
offset动态调整其位置,其中需要包括所有实例instances,以便未消失的消息框高度得以动态下滑。let offset = data.offset || 20; instances.forEach((item) => { item.$data.offset += instance.$el.offsetHeight + offset; }); instances.push(instance); -
第三步便是一个语法糖了,为其绑定一些便捷的函数:
["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); }; }); -
第四步,保证所有消息框消失后实例能对应消失,也即C语言的
malloc和free。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);
});