• 前端组件库造轮子——Message组件开发教程


    前端组件库造轮子——Message组件开发教程

    前言

    本系列旨在记录前端组件库开发经验,我们的组件库项目目前已在Github开源,下面是项目的部分组件。文章会详细介绍一些造组件库轮子的技巧并且最后会给出完整的演示demo。

    image.png

    文章旨在总结经验,开源分享,有问题的话也希望路过的大佬指正。

    组件开发流程

    样式和动画

    首先我们来考虑样式,对于message的调用我们下面再讲。

    样式的话,无非就是实现一个这样消息弹出框,同时加上弹出时的动画,这里借助vuetransition来实现。

    image.png

    这些都是简单的内容,代码量很少我就直接贴在这里了。

    <transition name="message-fade">
    <div class="message">
      <p class="message-content"></p>
    </div>
    </transition>
    
    .message {
      position: fixed;
      top: 20px;
      left: 50%;
      z-index: 50;
      box-sizing: border-box;
      display: flex;
      align-items: center;
      padding: 15px 15px 15px 20px;
      overflow: hidden;
      background-color: #f0f9eb;
      border: 1px solid #ebeef5;
      border-radius: 5px;
      transition: opacity 0.3s, transform 0.4s, top 0.4s;
      transform: translateX(-50%);
    }
    .message-fade-enter-active,
    .message-fade-leave-active {
      opacity: 0;
      transform: translate(-50%, -100%);
    }
    
    .message-content {
      color: #67c23a;
      font-size: 14px;
      margin: 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    当然现在这个message是只有空壳一样的内容,我们需要实现的效果是,有一个message函数接口,我们可以调用这个接口后弹出这个消息框。这其实就是和其它一些组件不太一样的地方,他的实现更倾向于一个接口函数一样。

    写一个函数当然不是问题,问题是怎么实现调用函数后渲染我们的消息框出来。

    这里就需要了解一下vue中渲染DOM的知识点了。

    h函数和render函数

    vue中,很多文件的开发都是在.vue文件的,这种文件开发是分为三大块来写,可以像类似写HTML时的感觉,这也是vue的卖点之一,让新手更易于上手。

    但是我们要知道,.vue实际上也是需要通过一些打包工具来编译成js代码才能执行。

    h函数就是把.vue中的代码编辑成一个虚拟DOM,最终会把template解析为render函数返回虚拟DOM,这点可以在Vue Dev Tools中看到:
    image.png

    也就是说,h函数是负责创建虚拟DOMrender是负责把这个虚拟DOM返回出去

    接口函数

    通过上面的介绍,大概不难猜出这个接口函数应该如何实现了,其实就是创建一个虚拟DOM出来包裹住我们的message组件,在利用render函数渲染出来即可。

    import element from "./message.vue";
    import { createVNode, render } from "vue";
    
    export default function message(options) {
      if (typeof options === "string") {
        options = {
          message: options as string,
        };
      }
      const params = {
        ...options,
      };
      
      // vue2 的写法
      // new Vue(render:() => createVNode(element)).mount();
      
      const div = document.createElement("div"); // 创建一个div
      const vnode = createVNode(element, params);  // 创建一个message组件的虚拟DOM
      render(vnode, div); // 渲染虚拟DOM
      document.body.appendChild(div.firstElementChild); // 加入到body中
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    那我们既然可以调用message接口函数了,那么在message组件中还有一些逻辑需要实现——在执行结束后关闭掉弹出来的消息框。

    这里就是利用v-show控制开关消息框,用定时器回调来解决关闭,我们可以利用props接收存在时间duration,这样我们的基本功能就算完成了,但是message组件还存在很多细节可以补充。

    // message.vue
    <transition name="message-fade">
        <div class="message" v-show="visible">
            <p class="message-content">{{ message }}</p>
        </div>
    </transition>
      
    const visible = ref(false);
    let timer = null;
    const start = () => {
      visible.value = true;
      if (timer !== null) {
        clearTimeout(timer);
      }
      if (props.duration > 0) {
        timer = setTimeout(() => {
          visible.value = false;
        }, props.duration);
      }
    };
    
    onMounted(() => {
      start();
    });
    
    onUnmounted(() => {
      if (timer !== null) {
        clearTimeout(timer);
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    回调删除节点的性能优化

    在刚刚上面完成的组件中,会发现当我们多次触发了message后,哪怕duration过了,节点也依然存在在body中,这些节点只是被隐藏了,并没有随着持续时间结束后删除掉。

    image.png

    这样显然是不太合理的,并且操作多了会存在一些性能问题,因此我们需要在这个组件在持续时间结束后可以被删除掉。

    这里我们可以在动画结束后,派发出一个destroy事件

     <transition name="message-fade" @after-leave="$emit('destroy')"> // 派发删除操作
        <div class="message" v-show="visible">
          <p class="message-content">{{ message }}</p>
        </div>
     </transition>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个$emit('destroy')会调用我们传进来的props中的onDestroy函数

    // message.ts
    
    const div = document.createElement("div");
    const vnode = createVNode(element, params);
    + vnode.props.onDestroy = () => { // 在参数props中挂载销毁函数
    +    render(null, div); // 利用render移除div节点
    + };
    render(vnode, div);
    document.body.appendChild(div.firstElementChild);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    连续多次弹出的用户体验优化

    当我们连续触发多次message时,会弹出多个消息,对于这多个消息,我们不希望会重叠在一起发生覆盖的情况,我们希望的是可以像下面这样。

    image.png

    那我们如何实现上面的效果呢?

    我们可以在props中加一个offset属性,该属性为message组件的离视屏顶部的距离。

    然后我们还需要知道上个节点的距离是多少,因此我们需要把连续点出的节点都记录起来,具体来说就是用一个数组把他们存起来,数组中的值按上一节点的offset基础上加合适的距离即可。

    // message.ts
    
    + const instances: VNode[] = [];
    export default function message(options) {
      ...
    
    +  let offset = options.offset || 20;
    
    +  instances.forEach((vnode: VNode) => {
    +    offset += vnode.el.offsetHeight + 20;
    +  });
    
      const params = {
        ...options,
    +    offset,
      };
      const div = document.createElement("div");
      const vnode = createVNode(element, params);
      vnode.props.onDestroy = () => {
        render(null, div); // render会移除dom,注意:此方法在vue2中无法使用
    +   instances.pop();
      };
      render(vnode, div);
      document.body.appendChild(div.firstElementChild);
    +  instances.push(vnode);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    同时,我们需要在渲染出message组件中,加上新的offset位置。

    // message.vue
    
    
        
    // 更改top位置

    {{ message }}

    const topStyle = computed(() => { return { top: `${props.offset}px`, }; });
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    演示demo

    完整项目demo

    结语

    Message组件的核心开发功能就是上面这些,其他更多的详细功能开发可以参考Hview-ui项目源码

    如果想要了解更多的组件轮子开发,或者组件库开发流程,更多详细的组件开发过程更新在GitHub项目源码,最后觉得我们项目or文章不错可以点个star,点点小手支持一下,也欢迎各路大佬为我们的开源项目添砖加瓦。

  • 相关阅读:
    Centos 8 安装 nginx
    人工智能算法工程师(高级)课程11-自然语言处理之NLP的语言模型-seq2seq模型,seq+注意力与代码详解
    电子笔记真的好用吗?手机上适合记录学习笔记的工具
    AWS与SAP扩大战略合作:通过AI增强ERP解决方案
    Bean——IOC(Github上有代码)
    Spring Boot中异步消息JMS的讲解与通信实例
    POC模拟攻击利器 —— Nuclei入门(一)
    获得京东商品详情 API
    pythorch的numel()函数计算模型大小与现存占用
    【新版】系统架构设计师 - 软件架构的演化与维护
  • 原文地址:https://blog.csdn.net/leoyongyuan/article/details/132707987