IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue+ts插件编写 -> 正文阅读

[JavaScript知识库]Vue+ts插件编写

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: [], // message组件实例数组(避免同时过多message导致重叠、出现位置错误问题)
  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);
      // 实例生命周期结束,清除绑定实例(发现清除实例后Vue会默认在原本实例的dom位置上增加一行注		 释<!-- -->,若要完全清除可能要全原生实现组件渲染和自定义生命周期)
      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插件,其中有错误内容还望互相交流斧正。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 17:21:08  更:2021-08-03 17:21:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:09:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码