手把手,跟着element-plus学loading组件

2,425 阅读3分钟

ps(活动是vant的loading组件,因为自己最近在造轮子,移动端用的也不多,所以写一个pc端的loading组件)

文章一共分3部分,第一部分先分析一下实现思路,第二部分阅读elemet-plus源码,第三部分自己实现一个loading组件.

组件效果跟element-plus类似,提供指令和service两种调用方式,可自定义背景,文本,图标。

image.png

代码地址

可在clone之后,启动docs:dev查看。

实现思路

loading组件的使用有两种方式,一种是通过v-loading,一种是通过service调用方式。

2种实现的逻辑应该是一样的,他们的区别在于。

1.参数来源不一样,一个是通过指令,一个是通过函数参数传值。

2.挂载的元素不同,一个是挂在指令元素上,一个是挂在body上。

所以我们可以分几个文件来实现,

directive来注册指令,以及获取loading组件的参数。

loading是模板文件,用来根据参数动态生成组件。

service是真正实现的入口,负责渲染组件。

image.png

至于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指令。

image.png

vue的指令注册,如果有不清楚的,可以查看一下vue官方文档,有详细的介绍。

我们接下去看看createInstance做了什么。

image.png

这里其实就做了两件事。

1.获取参数

使用指令的时候,参数有几个来源,一个是v-loading.fullscreen=""这种形式,参数在modifiers修饰符中,另一种是通过element属性的方式来获取,比如el-loading-text=""

ps(??操作符日常不太常见,意思是当左侧为null或者undefined时,则返回右侧的值,否则就返回左侧的值)

2.执行Loading方法创建组件

Loading方法位于service.ts文件,我们通过api调用,也是直接调用的Loaidng方法。

2.Loaidng创建组件函数

这一步,主要关注框起来的几步就行。

image.png

resolveOptions是同参数做处理,主要是target的兼容。

resolved.parent.appendChild(instance.$el)则是将dom挂载到指定targer下。

所以,重点就是createLoadingComponent创建组件方法。

3.组件模板

创建组件中,我们主要也是看红框中的逻辑。

image.png

elLoadingComponent定义loading组件模板,这种写法其实跟我们写template语法是类似的,只不过这种方式会灵活一些。

ps(h函数是创建虚拟节点的, 第一个参数为组件或者元素,第二个参数为属性,第三个参数为子节点,或者是插槽)

这里也贴一个官方的介绍

image.png

组件创建完成之后,通过mount把它挂载到一个div元素下。

仿写版本

手把手,仿写一个版本。

1.第一步,directive参数处理

这一步就直接照搬element-plus就行,只对个别参数处理做了简化的处理。

image.png

2.第二步,创建组件函数

这一步的resolveOptions参数处理,以及resolved.parent.appendChild(dom)挂载逻辑都是一样的,唯一不同的是组件的创建方式。

我这里使用h函数创建虚拟节点,然后通过render函数进行挂载。

参数通过props传递,支持插槽修改图标。 image.png

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代码到本地跑起来对比着看。

也欢迎留言讨论。