🍌 超简单!3步教你 Vue 自定义指令!【附源码】

454 阅读4分钟

大家好,我是前端架构师,关注微信公众号【程序员大卫】免费领取前端精品资料。

背景

在一些场景中,我们需要给页面元素添加加载效果,例如在数据请求时显示一个旋转的加载动画。Vue 提供了自定义指令的功能,让我们能够直接在 HTML 模板中应用加载效果,而不需要修改大量的组件代码。

通过本文的教程,你将会学会如何创建一个简单的 v-loading 指令,控制加载动画的显示,并能灵活修改加载动画的颜色和大小。

目录结构如下

src/
├── App.vue
├── main.ts
├── directive/
│   ├── index.ts              # 注册所有自定义指令
│   └── loading/
│       ├── index.ts          # v-loading 指令逻辑
│       ├── createLoading.ts  # 创建/更新/销毁 loading 实例
│       ├── Loading.vue       # loading 动画组件
│       └── types.ts          # 类型定义

步骤一:准备工作

首先,我们需要创建一个基本的 Vue 3 项目结构,并在其中实现一个加载动画,需要准备两个按钮:

  • 一个用来切换加载动画的显示/隐藏
  • 一个用来切换整个组件的销毁与否
<!-- App.vue -->
<script setup lang="ts">
import { ref } from "vue";

const loading = ref(true); // loading 用来控制加载动画显示
const isVisible = ref(true); // isVisible 用来控制组件销毁
</script>

<template>
  <!-- 控制加载动画的显示/隐藏 -->
  <button @click="loading = !loading">toggle visible</button>
  &nbsp;
  <!-- 控制组件的销毁 -->
  <button @click="isVisible = !isVisible">toggle destroy</button>

  <!-- 显示加载动画的区域 -->
  <div
    v-if="isVisible"
    v-loading="loading"
    loading-color="red"
    loading-size="60"
    class="section"
  ></div>
</template>

<style scoped>
.section {
  width: 200px;
  height: 200px;
  border: 1px solid red;
  margin-top: 20px;
}
</style>

步骤二:创建 v-loading 自定义指令

接下来,我们需要实现 v-loading 自定义指令的逻辑。这个指令将会根据传入的参数控制加载动画的显示状态,并根据元素的属性来动态更新动画的大小和颜色。

首先,我们定义一个 createLoading 函数来创建加载动画,并通过指令的生命周期钩子来管理它:

1. createLoading.ts 文件

/**
 * 创建、更新和销毁 v-loading 的实例
 * @file directive/loading/createLoading.ts
 */

import { defineComponent, h, reactive, render } from "vue";
import type { LoadingProps } from "./types";
import Loading from "./Loading.vue";

export function createLoading(
  props: Partial<LoadingProps>,
  target: HTMLElement
) {
  const data = reactive({
    ...props,
  });

  const Comp = defineComponent({
    render: () => h(Loading, data),
  });

  // 创建虚拟节点
  const vNode = h(Comp);
  // 将虚拟节点挂载到容器上
  render(vNode, target);

  return {
    update: (newProps: Partial<LoadingProps>) => Object.assign(data, newProps),
    destroy: () => render(null, target),
  };
}

2. Loading.vue 文件

Loading.vue 组件负责展示加载动画。我们通过 sizecolor 属性控制加载动画的大小和颜色。

⚠️注:Loading 动画相关代码可以从下面网站拷贝。

css-loaders.com/spinner/

<!--
  loading 动画组件
  @file directive/loading/Loading.vue
-->

<script setup lang="ts">
import { computed } from "vue";
import type { LoadingProps } from "./types";

// 定义组件的 props,支持传递大小、颜色和是否显示
const props = withDefaults(defineProps<Partial<LoadingProps>>(), {
  isVisible: true,
  size: 30,
  color: "#25b09b",
});

// 计算加载动画的实际大小(单位:px)
const sizePx = computed(() => props.size + "px");
</script>

<template>
  <div class="loader-box" v-if="isVisible">
    <div class="loader"></div>
  </div>
</template>

<style scoped>
.loader-box {
  display: flex;
  width: 100%;
  height: 100%;
  justify-content: center;
  align-items: center;
}

.loader {
  width: v-bind("sizePx");
  height: v-bind("sizePx");
  background: v-bind("color");
  padding: 4px;
  border-radius: 50%;
  --_m: conic-gradient(#0000 10%, #000), linear-gradient(#000 0 0) content-box;
  -webkit-mask: var(--_m);
  mask: var(--_m);
  -webkit-mask-composite: source-out;
  mask-composite: subtract;
  animation: l3 1s infinite linear;
}
@keyframes l3 {
  to {
    transform: rotate(1turn);
  }
}
</style>

3. types.ts 文件

为了让 Loading.vue 组件的属性更清晰,我们定义一个类型 LoadingProps 来描述它的属性结构。

/**
 * 类型定义
 * @file directive/loading/type.ts
 */

export interface LoadingProps {
  isVisible: boolean;
  size: number;
  color: string;
}

4. 定义 loading 指令核心逻辑

/**
 * v-loading 指令逻辑
 * @file directive/loading/index.ts
 */

import type { Directive, DirectiveBinding } from "vue";
import { createLoading } from "./createLoading";

interface El extends HTMLElement {
  __loadingInstance: ReturnType<typeof createLoading>;
}

type Binding = DirectiveBinding<boolean>;

// 获取指令传递的参数,包含动画大小、颜色和是否可见
const getProps = (el: El, binding: Binding) => {
  const size = el.getAttribute("loading-size");
  const color = el.getAttribute("loading-color");
  return {
    size: size === null ? undefined : Number(size),
    color: color === null ? undefined : color,
    isVisible: binding.value,
  };
};

export const loading: Directive = {
  mounted(el: El, binding: Binding) {
    console.log("mounted");
    const props = getProps(el, binding);
    const instance = createLoading(props, el);
    el.__loadingInstance = instance;
  },
  updated(el: El, binding: Binding) {
    console.log("updated");
    const props = getProps(el, binding);
    el.__loadingInstance.update(props);
  },
  unmounted(el: El) {
    console.log("unmounted");
    el.__loadingInstance.destroy();
  },
};

步骤三:注册指令

在 Vue 3 中,我们需要在应用中注册自定义指令。我们将 v-loading 指令注册到 Vue 应用中,以便可以在模板中直接使用。

1. setupDirective.ts 文件

/**
 * 注册所有自定义指令
 * @file directive/index.ts
 */

import type { App } from "vue";
import { loading } from "./loading";

// 用来在 Vue 应用中注册指令的函数
export function setupDirective(app: App<Element>) {
  app.directive("loading", loading);
}

2. 主应用入口 main.ts

在应用的入口文件中,我们导入并调用 setupDirective 来注册指令。

import { createApp } from "vue";
import App from "./App.vue";
import { setupDirective } from "./directive";

// 创建 Vue 实例
const app = createApp(App);

// 注册自定义指令
setupDirective(app);

// 挂载应用
app.mount("#app");

总结

通过这三个简单的步骤,我们成功实现了一个 v-loading 自定义指令,能够在 Vue 应用中控制加载动画的显示与隐藏。通过自定义指令,我们不仅可以在模板中方便地添加功能,还能灵活控制加载动画的样式。

希望你能通过这篇文章理解如何使用 Vue 自定义指令来提升你的开发效率!

源码地址:

github.com/zm8/wechat-…