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

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

前言

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

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

组件开发流程

样式和动画

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

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

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

ts 复制代码
<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;
}

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

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

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

h函数和render函数

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

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

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

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

接口函数

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

ts 复制代码
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中
}

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

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

ts 复制代码
// 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);
  }
});

回调删除节点的性能优化

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

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

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

ts 复制代码
 <transition name="message-fade" @after-leave="$emit('destroy')"> // 派发删除操作
    <div class="message" v-show="visible">
      <p class="message-content">{{ message }}</p>
    </div>
 </transition>

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

ts 复制代码
// 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);

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

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

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

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

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

ts 复制代码
// 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);
}

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

typescript 复制代码
// message.vue

<transition name="message-fade" @after-leave="$emit('destroy')">
    <div class="message" v-show="visible" :style="topStyle"> // 更改top位置
      <p class="message-content">{{ message }}</p>
    </div>
</transition>
  
const topStyle = computed(() => {
  return {
    top: `${props.offset}px`,
  };
});

演示demo

完整项目demo

结语

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

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

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax