这是我参与「第五届青训营」伴学笔记创作活动的第 12 天
Message组件
这次项目的开发是组件库,Message组件在组件开发中还是比较有特点的。具体的来说,它和一般组件不太一样,因为在实际操作时是用一个api来完成的,这就是我选择记录这个组件的原因。这个组件也会让我们学到更多框架内核的东西,例如渲染函数,虚拟节点等。
对于这么一个组件,我们需要大致把他拆分为三块,接口,组件样式搭载和渲染api。
在这个文章中,样式布局参考了element-plus,架构语言是vue3+ts,废话不多说,开始吧。
接口
首先开发组件库一定会需要考虑接口,这个组件可以传入什么值?需要满足什么功能都是依靠接口来完成的。
例如,在我们使用button组件时,可以利用type的参数传入button的样式类型。
在这里,我们用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>;
这里定义的接口有点多,我们下面做组件是会一步一步来操作。
开发组件
接下来我们就可以开发组件的模板了,例如这样的效果。
基础结构
我们的组件效果是要可以弹窗,并且消失。这就需要用到动画了。
在这里我们可以用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中看到:
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隐藏了,并没有删除这个结点。
这种效果显然不能让人满意,接下来我们介绍如何删除这个结点。
删除渲染完成的结点
我们可以在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组件的大概开发思路。接下来的内容无非是在这上面加加加样式啥的。
最后附上完整代码地址:
如果觉得有帮助的话,请给我们项目点个star吧。