- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第42期,链接:juejin.cn/post/720672…
ps(活动是vant的loading组件,因为自己最近在造轮子,移动端用的也不多,所以写一个pc端的loading组件)
文章一共分3部分,第一部分先分析一下实现思路,第二部分阅读elemet-plus源码,第三部分自己实现一个loading组件.
组件效果跟element-plus类似,提供指令和service两种调用方式,可自定义背景,文本,图标。
代码地址。
可在clone之后,启动docs:dev查看。
实现思路
loading组件的使用有两种方式,一种是通过v-loading,一种是通过service调用方式。
2种实现的逻辑应该是一样的,他们的区别在于。
1.参数来源不一样,一个是通过指令,一个是通过函数参数传值。
2.挂载的元素不同,一个是挂在指令元素上,一个是挂在body上。
所以我们可以分几个文件来实现,
directive来注册指令,以及获取loading组件的参数。
loading是模板文件,用来根据参数动态生成组件。
service是真正实现的入口,负责渲染组件。
至于loading效果,可以渲染个icon,然后加个简单的动画效果
.bu-load-loop {
animation: ani-load-loop 1s linear infinite;
}
@keyframes ani-load-loop {
from {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
to {
transform: rotate(360deg);
}
}
element-plus是如何实现loading的?
文件目录上文已经分析过了,我们看看他们具体做了哪些事?
1.directive全局注册
directive方法导出一个vLoaidng指令。
vue的指令注册,如果有不清楚的,可以查看一下vue官方文档,有详细的介绍。
我们接下去看看createInstance做了什么。
这里其实就做了两件事。
1.获取参数
使用指令的时候,参数有几个来源,一个是v-loading.fullscreen=""这种形式,参数在modifiers修饰符中,另一种是通过element属性的方式来获取,比如el-loading-text=""
ps(??操作符日常不太常见,意思是当左侧为null或者undefined时,则返回右侧的值,否则就返回左侧的值)
2.执行Loading方法创建组件
Loading方法位于service.ts文件,我们通过api调用,也是直接调用的Loaidng方法。
2.Loaidng创建组件函数
这一步,主要关注框起来的几步就行。
resolveOptions是同参数做处理,主要是target的兼容。
resolved.parent.appendChild(instance.$el)则是将dom挂载到指定targer下。
所以,重点就是createLoadingComponent创建组件方法。
3.组件模板
创建组件中,我们主要也是看红框中的逻辑。
elLoadingComponent定义loading组件模板,这种写法其实跟我们写template语法是类似的,只不过这种方式会灵活一些。
ps(h函数是创建虚拟节点的, 第一个参数为组件或者元素,第二个参数为属性,第三个参数为子节点,或者是插槽)
这里也贴一个官方的介绍
组件创建完成之后,通过mount把它挂载到一个div元素下。
仿写版本
手把手,仿写一个版本。
1.第一步,directive参数处理
这一步就直接照搬element-plus就行,只对个别参数处理做了简化的处理。
2.第二步,创建组件函数
这一步的resolveOptions参数处理,以及resolved.parent.appendChild(dom)挂载逻辑都是一样的,唯一不同的是组件的创建方式。
我这里使用h函数创建虚拟节点,然后通过render函数进行挂载。
参数通过props传递,支持插槽修改图标。
3.第三步,组件模板
loaidng模板也被我改成了.vue文件
<template>
<transition name="bu-loading-fade">
<div
class="bu-loading-mask"
v-show="visible"
:class="[customClass, fullscreen ? 'is-fullscreen' : '']"
:style="{ backgroundColor: backgroundColor as string }"
>
<div class="bu-loading-spinner">
<span v-if="$slots.spinner" class="bu-loading-svg bu-load-loop">
<slot name="spinner"></slot>
</span>
<svg v-else aria-hidden="true" class="bu-loading-svg bu-load-loop">
<use :xlink:href="`#icon-loading`" fill="#2d8cf0"></use>
</svg>
<div class="bu-loading-text">
{{ text }}
</div>
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import type { Ref } from 'vue';
import type { MaybeRef } from '@vueuse/core';
defineProps<{
backgroundColor: MaybeRef<string>;
text: MaybeRef<string>;
fullscreen: boolean;
customClass: MaybeRef<string>;
visible: Ref<boolean>;
}>();
</script>
至此,loading组件基本完成。
最后再全局注册一下就行。
export const BuLoading = {
install(app: App) {
app.directive('loading', vLoading);
app.config.globalProperties.$loading = Loading;
},
service: Loading,
};
总结
虽然是一个简单的loading组件,但是细节还是挺多的。
1.全局指令的注册,以及参数的处理
2.h函数,render函数等api的使用。
3.ts类型检测以及实现
....
因为篇幅原因,文章只能梳理主逻辑跟脉络,有些细节很难展开。
如果对实现的逻辑有疑问,建议先收藏文章,clone代码到本地跑起来对比着看。
也欢迎留言讨论。