组件库开发——Message组件 | 青训营笔记

221 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 12 天

Message组件

这次项目的开发是组件库,Message组件在组件开发中还是比较有特点的。具体的来说,它和一般组件不太一样,因为在实际操作时是用一个api来完成的,这就是我选择记录这个组件的原因。这个组件也会让我们学到更多框架内核的东西,例如渲染函数,虚拟节点等。

对于这么一个组件,我们需要大致把他拆分为三块,接口组件样式搭载渲染api

在这个文章中,样式布局参考了element-plus,架构语言是vue3+ts,废话不多说,开始吧。

接口

首先开发组件库一定会需要考虑接口,这个组件可以传入什么值?需要满足什么功能都是依靠接口来完成的。

例如,在我们使用button组件时,可以利用type的参数传入button的样式类型。

image.png

在这里,我们用vue中的props来传递参数。

import { ExtractPropTypes } from "vue";

export const MessageType = ["success", "info", "warning", "error"];

export const MessageProps = {
  message: String,   // 内容
  type: { // 样式类型
    type: String,
    validator(value: string) {
      return MessageType.includes(value);
    },
    default: "info",
  },
  duration: {  // 维持时间
    type: Number,
    default: 3000,
  },
  center: {   // 是否居中
    type: Boolean,
    default: false,
  },
  dangerouslyUseHTMLString: {   // message中能否传入HTML
    type: Boolean,
    default: false,
  },
  showClose: {     // 展示关闭按钮
    type: Boolean,
    default: false,
  },
  onClose: {      // 删除前的回调函数接口
    type: Function,
  },
};

export type MessageProps = ExtractPropTypes<typeof MessageProps>;

这里定义的接口有点多,我们下面做组件是会一步一步来操作。

开发组件

接下来我们就可以开发组件的模板了,例如这样的效果。 image.png

基础结构

我们的组件效果是要可以弹窗,并且消失。这就需要用到动画了。

在这里我们可以用vue给我们提供的transition来做。

基础Message的骨架`

<transition name="h-message-fade">
<div v-show="visible" :class="['h-message', type ? `h-message--${type}` : '', center ? 'is-center' : '']">
  <i :class="`h-message__icon h-icon-${type}`"></i>
  <p v-else v-html="message" class="h-message__content"></p>
</div>
</transition>
import { MessageProps } from "./message";
import { toRefs, onMounted, ref, onUnmounted } from "vue";
const props = defineProps(MessageProps);
// 解构出我们的props参数
const { message, showClose, type, duration, center, dangerouslyUseHTMLString, onClose } = toRefs(props); 

let visible = ref(true);

这里给出动画的css样式

.h-message {
  transform: translateX(-50%);
  transition: opacity 0.3s, transform 0.4s, top 0.4s;
}

.h-message-fade-enter-active,
.h-message-fade-leave-active {
  opacity: 0;
  transform: translate(-50%, -100%);
}

🆗,到此我们的基础结构就实现完成了,接下来就是一些交互逻辑。

实现duration展示结点

很简单,用一个定时器挂载记录时间duration即可。

let timer = null;

const startTimer = () => {
    visible.value = true;
    if (duration.value) {
        setTimeout(() => {
            visible.value = false;
        }, duration.value)
    }
}

// 一开始就调用显示message
onMounted(() => {
  startTimer();
});

// 结束后删除定时器
onUnmounted(() => {
  clearTimeout(timer);
});

增加关闭按钮

<transition name="h-message-fade">
<div v-show="visible" :class="['h-message', type ? `h-message--${type}` : '', center ? 'is-center' : '']">
  <i :class="`h-message__icon h-icon-${type}`"></i>
  <p v-else v-html="message" class="h-message__content"></p>
  <i v-if="showClose" class="h-message__closeBtn h-icon-close1" @click="close"></i>
</div>
</transition>
const close = () => {
  visible.value = false;
};

渲染组件

接下来我们就需要把组件变成函数接口了。

如果之前没接触过的组件开发的同学可能不清楚,为什么要有这个操作。很简单,举个例子。

当你用ui库的时候,你用button组件只需要在template上加

// h 是我和小伙伴开发的库,前缀加h
<h-button>按钮</h-button>

但是,你想想你使用message组件也是这样操作的吗?不,你使用的时候是这样,类似操作一个函数的方法。

$message({
    message: "This is a success type.",
    type: "success",
    duration: 3000,
});

所以,这就是api组件的特殊之处,我们要把组件完成这个操作,就需要借助h函数来完成。

h函数和render

接下来我简单介绍下h函数和render函数

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

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

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

image.png

h函数可以接受三个参数

  • 1.结点,这个参数可以是vue组件,也可以是DOM结点。
  • 2.结点上的参数
  • 3.改结点下的子节点,可以用一个数组表示多个子节点

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

题外话,render函数其实在vue3时经常使用,当你用setup时,在最后会return出来一些数据,这个return 就是起到和render函数类似的效果。

实现渲染函数

import element from "./message.vue";
import { h, render } from "vue";

export default function message(options: any) {
  if (typeof options === "string") {
    options = {
      message: options,
    };
  }

  // vue3写法,需要用虚拟节点
  const div = document.createElement("div");
  // 渲染组件
  const vnode = h(element, options);
  // 挂载到div上
  render(vnode, div);
  // 加入页面
  document.body.appendChild(div.firstElementChild!);
}

这个思路很简单,获取到我们刚刚的message组件,然后用h函数对齐进行封装,这样当我们获取到Message时,就是一个api函数了。

为了防止有萌新宝宝不懂这里要用div干嘛,我解释一下div的操作。

我们创建一个div,然后把message组件挂载在div中,并且需要注意要加在body后面,用于在整个DOM树上才能看到message组件。

到此为止,我们的组件已经完成了,这时候已经能正常实现渲染了。

但是还有一个问题,就是我们渲染完成后,body页面的div还依然存在。是的,在message组件中我们只是用v-show隐藏了,并没有删除这个结点。

image.png

这种效果显然不能让人满意,接下来我们介绍如何删除这个结点。

删除渲染完成的结点

我们可以在message渲染完毕后,调用事件触发来删除结点。具体的,我们可以在message组件中emit一个事件给父组件,通知已经渲染完成可以删除了。

在这里,transition刚好有一个事件结束完成的api接口,我们可以把emit挂载在上面。

<transition name="h-message-fade"  @after-leave="$emit('destroy')">
<div v-show="visible" :class="['h-message', type ? `h-message--${type}` : '', center ? 'is-center' : '']">
  <i :class="`h-message__icon h-icon-${type}`"></i>
  <p v-else v-html="message" class="h-message__content"></p>
</div>
</transition>

同时,我们在父组件这里接收这个事件,并且运行。

import element from "./message.vue";
import { h, render } from "vue";

export default function message(options: any) {
  if (typeof options === "string") {
    options = {
      message: options,
    };
  }

  const userClose = options.onClose;
  const opts = {
    ...options,
    onClose: () => {
      userClose && userClose();
    },
  };

  // vue3写法,需要用虚拟节点
  const div = document.createElement("div");
  // 渲染组件
  const vnode = h(element, opts);
  vnode.props!.onDestroy = () => {
    render(null, div); // render会移除dom,注意:此方法在vue2中无法使用
  };
  // 挂载到div上
  render(vnode, div);
  // 加入页面
  document.body.appendChild(div.firstElementChild!);
}

总结:

到此,整个组件就完成了。这就是整个message组件的大概开发思路。接下来的内容无非是在这上面加加加样式啥的。

最后附上完整代码地址:

github项目代码

message组件演示效果

如果觉得有帮助的话,请给我们项目点个star吧。