1. 为什么要写插件
? 一开始我也是以为Vue插件离我的日常开发很遥远的,但直到有一天公司用的组件库换了,换成了MD风格的Vuetify。
? 这个组件库优点就是好看、且各种插槽props可以充分满足自定义需求;缺点也很明显:在用惯了Element的人看来,这个组件库不但缺少了很多全局函数,如$message,而且自定义的插槽、Prop太多了,需要一段时间熟悉。其中尤其是缺少了函数式组件让我很难受,虽然可以通过顶层写个message组件来解决,但用起来还是太麻烦了。
? 于是再三思索下我决定自己用渲染函数封装个函数式组件。
2. 组件编写
? Vue的组件封装还是很友好的,render能够直接渲染写好的Vue模板并把Prop传进去,就像下面这样:
Vue.extend({
data() {
return {
...
};
},
render(h): VNode {
const props = {
...
};
return h(Component, { props });
},
});
? 看上去和单文件组件也没太大区别嘛,其实Element也是这么实现的函数式组件的。
1. Vuetify封装问题
? 尽管使用的Vuetify,但Vue模板中不能使用Vuetify组件。原因在于Vuetify对组件库中的所有组件的封装方法会生成一个递归调用自身的实例,详情可以在console中打印vuetify组件dom查看,Vuetify组件会带有一个$vuetify属性,其中递归放置了它自身,初步猜测是为了防止被二次封装。
? 因此Vue模板中需要使用原生实现。具体实现不在此赘述,可以前往npm下载本项目查看。
2. 同时使用typescript的问题
? 由于ts中无法直接在Vue实例上挂载额外的全局方法,需要编写声明文件实现,在声明文件中,需要添加以下代码将$message方法声明到Vue实例上:
declare module "vue/types/vue" {
interface Vue {
$message(options: MessageOptions): void;
}
}
? 同时也必需引入此模块,否则仍会报错未找到模块。引入此模块后会提示模块未使用,忽略即可。
3. 插件注册
? Vue中使用install函数定义插件,Vue.use会自动注入install方法的内容,install方法中第一个参数为注入的Vue实例,第二个参数为可选项。
? 具体实现如下:
const MESSAGE: PluginObject<any> = {
duration: 2000,
animateTime: 600,
instances: [],
install(Vue, globalOptions: GlobalOptions = {}) {
function msg(options: MessageOptions) {
const currOptions: GlobalOptions = {};
currOptions.verticalOffset =
globalOptions.verticalOffset === 0 || globalOptions.verticalOffset
? globalOptions.verticalOffset
: 40;
currOptions.flat = globalOptions.flat || false;
currOptions.position = globalOptions.position || "right";
currOptions.width = globalOptions.width || "30%";
const VueMessage = Vue.extend({
data() {
return {
show: false,
};
},
render(h): VNode {
const props = {
type: options.type,
message: options.message,
show: this.show,
flat: currOptions.flat,
position: currOptions.position,
width: currOptions.width,
};
return h(Message, { props });
},
});
const newMessage = new VueMessage();
let vm: typeof newMessage | null = newMessage.$mount();
const el = vm.$el;
document.body.appendChild(el);
let verticalOffset = currOptions.verticalOffset;
const offsetTopArr: number[] = [];
if (MESSAGE.instances.length) {
const itemHeight = MESSAGE.instances[0].$el.offsetHeight + 8;
MESSAGE.instances.forEach((item: any) => {
offsetTopArr.push(item.$el.offsetTop);
});
offsetTopArr.sort((prev, next) => prev - next);
if (offsetTopArr[0] !== verticalOffset) {
offsetTopArr.unshift(verticalOffset - itemHeight);
}
verticalOffset =
offsetTopArr.reduce((prev, next) => {
if (next - prev > itemHeight) {
return prev;
} else {
return next;
}
}) + itemHeight;
}
const child: any = vm.$children[0];
child.verticalOffset = verticalOffset;
vm.show = true;
MESSAGE.instances.push(vm);
const t1 = setTimeout(() => {
clearTimeout(t1);
vm!.show = false;
const t2 = setTimeout(() => {
clearTimeout(t2);
document.body.removeChild(el);
newMessage.$destroy();
vm = null;
options.callback && options.callback();
MESSAGE.instances.shift();
}, MESSAGE.animateTime);
}, MESSAGE.duration);
}
Vue.prototype.$message = msg;
},
};
? 至此整个插件就编写完成了,限于篇幅,一些interface、模板实现没有介绍,感兴趣的可以到npm下载ca-vuetify-message组件查看源码。
? 由于本人也是第一次编写Vue插件,其中有错误内容还望互相交流斧正。
|