vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3封装全局函数式组件

Vue3封装全局函数式组件方法总结

作者:前端咸鱼翻身

函数式组件就是没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法,它只是一个接受一些 prop 的函数,下面这篇文章主要给大家介绍了关于Vue3封装全局函数式组件方法的相关资料,需要的朋友可以参考下

前言

相信大家在 Vue 中考虑复用逻辑的时候经常使用组件化开发,也肯定使用过函数式组件,就是那种在 js 中也能够导入调用的组件。那么如何去封装这么一个函数式组件呢,这篇文章将采用Toast组件简单介绍一下封装的方法,封装之后就能大大提高我们开发的效率了。

一、函数式组件是什么?

简单介绍一下声明式组件与函数式组件,大多数时候我们引入组件都采用声明式的的方式,这里以 Vant 组件库为例,类似 Button 按钮这种就是声明式组件:

<van-button type="primary">主要按钮</van-button>

还有类似 <TheWelcome /> 这种自定义名称且在 .vue 文件里引用其他 .vue 文件的就是声明式组件

<template>
  <main>
    <TheWelcome />
  </main>
</template>
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue';
</script>

而函数式组件则是通过调用 API 的方式快速唤起全局的组件,还是以 Vant 组件库为例,比如使用 Toast 组件,调用函数后会直接在页面中渲染对应的轻提示:

import { showToast } from 'vant';
showToast('提示内容');

通常我们使用函数式组件是在某个交互完成时触发,又或者是在非.vue文件里唤起全局的组件,例如封装axios,在axios.js中使用Toast组件显示报错信息:

showToast('服务器响应超时,请刷新当前页');

二、创建一个函数式组件

下面将创建一个自己定义的toast组件,由于这个toast组件默认是显示成功的,所以称之为“okToast”,先展示一下调用后的效果:

1. 封装toast组件

与创建声明式组件一致,在.vue文件里定义好组件接收的参数还有组件的样式。代码如下(示例):

<template>
  <!-- 加一点动画效果 -->
  <transition name="toast" @after-leave="onAfterLeave">
    <div class="toast" v-if="isShow" :style="{ width: toastWidth }">
      <!-- 手动点击隐藏弹窗 -->
      <div v-if="time < 0" class="cancel" @click="hidden"></div>
      <img
        v-if="type === 'success' || type === 'icon'"
        class="img"
        src="../../assets/images/7vip_web_toast_finish_icon_40x40@2x.png"
        alt="success"
      />
      <img v-if="type === 'warn'" class="img" src="../../assets/images/7vip_web_toast_warn.png" alt="warn" />
      <div v-if="content && type !== 'icon'" class="content" :style="{ textAlign }">{{ content }}</div>
    </div>
  </transition>
</template>
<script setup>
  import { ref, computed } from "vue";
  const props = defineProps({
    //文案内容,默认success
    content: {
      type: String,
      default: "success",
    },
    //显示时间,默认2s,传小于0的值不自动消失,需要手动关闭
    time: {
      type: Number,
      default: 2000,
    },
    //宽度,默认310px,这里考虑传入的宽度可以用字符串也可以用数值,所以没有定义类型
    width: {
      default: 310,
    },
    //弹窗文案文本对齐方式,默认center
    textAlign: {
      type: String,
      default: "center",
    },
    //类型,默认图标(√),传'warn'显示(!),传其他值则不显示icon,传'icon'不显示文本
    type: {
      type: String,
      default: "success",
    },
    //接收的函数方法
    hide: {
      type: Function,
      default: () => {},
    },
  });
  // 弹窗显隐控制
  const isShow = ref(false);
  // 宽度控制,由于设计稿宽度是750px的宽度,这里通过计算属性,根据设备屏幕宽度自适应显示弹窗的宽度
  const toastWidth = computed(() => (parseInt(props.width.toString()) / 750) * document.documentElement.clientWidth + "px");
  // 显示弹窗方法
  const show = () => {
    isShow.value = true;
    if (props.time >= 0) {
      setTimeout(() => {
        isShow.value = false;
      }, props.time);
    }
  };
  // 隐藏弹窗方法
  const hidden = () => {
    isShow.value = false;
  };
  // 弹窗关闭后等动画结束再调用卸载逻辑
  const onAfterLeave = () => {
	props.hide();
  };
  // 将显示弹窗方法暴露出去
  defineExpose({
    show,
  });
</script>
<style lang="scss" scoped>
  .toast-enter-active,
  .toast-leave-active {
    transition: opacity 0.3s ease-out;
  }
  .toast-enter-from,
  .toast-leave-to {
    opacity: 0;
  }
  .toast {
    position: fixed;
    top: 45%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 99;
    background: #333333;
    border-radius: 20px;
    padding: 40px;
    text-align: center;
    .cancel {
      background: url("../../assets/images/quxiao@2x.png") no-repeat center / contain;
      position: absolute;
      top: 10px;
      right: 10px;
      width: 40px;
      height: 40px;
      &::before {
        content: "";
        position: absolute;
        top: -10px;
        right: -10px;
        bottom: -10px;
        left: -10px;
      }
    }
    .img {
      width: 80px;
      height: 80px;
    }
    .content {
      margin-top: 20px;
      font-size: 32px;
      color: #ffcc99;
      line-height: 30px;
      text-align: initial;
    }
  }
</style>

2. 创建应用实例

这是最关键的步骤,在 Vue2 的时候封装函数式组件使用的是 Vue.extend,利用这个基础的 Vue 构造器,能创建Vue子类实例,然而在 Vue3 官方删除了这个方法,但是也提供了新的api: createApp 给我们使用,利用 createApp 就能创建 Vue 应用实例了。代码如下(示例):

import { createApp } from "vue";
import OkToast from "./okToast.vue";
const okToast = options => {
  // 创建元素节点
  const rootNode = document.createElement("div");
  // 在body标签内部插入此元素
  document.body.appendChild(rootNode);
  // 创建应用实例(第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props)
  const app = createApp(OkToast, {
    ...options,
    hide() {
      // 卸载已挂载的应用实例
      app.unmount();
      // 删除rootNode节点
      document.body.removeChild(rootNode);
    },
  });
  // 将应用实例挂载到创建的 DOM 元素上
  return app.mount(rootNode);
};
// 注册插件app.use()会自动执行install函数
okToast.install = app => {
  // 注册全局属性,类似于 Vue2 的 Vue.prototype
  app.config.globalProperties.$okToast = options => okToast(options).show();
};
// 定义show方法用于直接调用
okToast.show = options => okToast(options).show();
export default okToast;

3. 注册插件(可省略)

代码如下(示例):

// main.js
import okToast from './plugins/okToast/index';

app.use(okToast);

Q&A: 补充一些注释

①:为什么采用调用函数方法的方式去控制显隐

答:目的是为了那个显示与消失的动画效果,当组件创建后需要组件内 ”isShow“ 产生变化才能触发<Transition> 的动画效果,所以这里写了show函数方法。

②:函数式组件的这两个文件之间的联系

答:简单来说,js文件传参数及函数给vue文件,均可在 createApp 的第二个参数中传递,vue文件相当于子组件,使用props的方式接收;vue文件传值及函数给js文件,可以通过 defineExpose 方法暴露出去,js文件中在应用实例创建完成后,就能拿到暴露出来的属性及方法。

三、调用

1. 注册插件后在.vue文件内获取全局方法

<script setup>
import { getCurrentInstance } from 'vue';
// 获取当前实例,在当前实例相当于 vue2 中的 this
const { proxy }: any = getCurrentInstance();
// 最简单的调用方式,即可出来开头所展示的效果
proxy.$okToast();
// 传递自定义参数,与okToast.vue文件接收的参数对应
setTimeout(() => {
  proxy.$okToast({
	content: 'Hello World'
  });
}, 2000);
</script>

2. 可不注册插件,在.vue或.js文件内直接调用方法

import $okToast from "./plugs/okToast";

$okToast.show({
  type: "warn",
  content: "Network error,try again later",
});

四、优化改进

上面封装的Toast组件在创建多个实例的时候,它们之间是互不干扰的,不会存在组件参数异常的情况。那么实际观察 DOM 元素我们会发现其在 DOM 上是存在多个的,只不过当多次调用的时候,后面的会把前面还没消失的Toast覆盖了,这样效果可能不那么友好。那么就存在两个优化方向:一是当后续出现Toast的时候结束掉前面出现的Toast,二是调整后续Toast出现的位置。

1、单例模式(推荐)

先上代码(示例):

let rootNode = null;
let app = null;
const okToast = options => {
  const dom = document.body.querySelector('.my-ok-toast');
  if (!dom) {
    rootNode = document.createElement('div');
    // 给创建的元素设置 class 属性值
    rootNode.className = `my-ok-toast`;
    document.body.appendChild(rootNode);
  } else {
    // If you want to mount another app on the same host container, you need to unmount the previous app by calling `app.unmount()` first.
    app.unmount();
  }
  app = createApp(OkToast, {
    ...options,
    hide() {
      // 卸载已挂载的应用实例
      if (app) {
        app.unmount();
        app = null;
      }
      // 删除rootNode节点
      if (rootNode) {
        document.body.removeChild(rootNode);
        rootNode = null;
      }
    }
  });
  return app.mount(rootNode);
};

效果展示:

请添加图片描述

怎么去结束前面出现的Toast呢,我们只需要确保全局只渲染一个Toast弹窗就行,所以可以使用单例模式,单例模式即一个类只能有一个实例。类似Vant的Toast组件,其默认采用了单例模式,即同一时间只会存在一个,这种做法应该是普遍的弹窗做法。

2、多个提示弹窗

先上代码(示例):

// 创建临时变量保存高度值
let top = 0;
const okToast = options => {
  const rootNode = document.createElement('div');
  // 给创建的元素设置 class 属性值
  rootNode.className = `my-ok-toast`;
  document.body.appendChild(rootNode);
  const dom = document.body.querySelector('.my-ok-toast');
  // 若DOM中存在该元素则将新元素高度往下移动
  if (dom) {
    top += 120;
    rootNode.style.top = 80 + top + 'px';
  }
  const app = createApp(OkToast, {
    ...options,
    hide() {
      app.unmount();
      document.body.removeChild(rootNode);
    }
  });
  return app.mount(rootNode);
};

再将css样式添加到全局上

.my-ok-toast {
  position: fixed;
  z-index: 99;
  top: 80px;
  left: 50%;
  transform: translateX(-50%);
}

效果展示:

请添加图片描述

这里的做法提供给大家一种思路,实际的动画效果还有待优化,由于本文篇幅有限所以就不展开了,以后遇到这种需求再深入探索吧。

总结

以上就是全部内容,本文简单介绍了 Vue3 函数式组件的封装方法,将其以插件的方式使用app.use() 方法安装在 Vue 上,使其作为全局功能的工具,这就是 Vue3 中逻辑复用的插件 (Plugins) 写法。

如果此篇文章对您有帮助,欢迎您【点赞】、【收藏】!也欢迎您【评论】留下宝贵意见,共同探讨一起学习~

扩展阅读

  1. Vue3 插件
  2. Vant Toast 轻提示

到此这篇关于Vue3封装全局函数式组件方法的文章就介绍到这了,更多相关Vue3封装全局函数式组件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

阅读全文