Vue3封装全局函数式组件方法总结
2023-06-20 10:47:33
作者:前端咸鱼翻身
函数式组件就是没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法,它只是一个接受一些 prop 的函数,下面这篇文章主要给大家介绍了关于Vue3封装全局函数式组件方法的相关资料,需要的朋友可以参考下
相信大家在 Vue 中考虑复用逻辑的时候经常使用组件化开发,也肯定使用过函数式组件,就是那种在 js 中也能够导入调用的组件。那么如何去封装这么一个函数式组件呢,这篇文章将采用Toast组件简单介绍一下封装的方法,封装之后就能大大提高我们开发的效率了。
一、函数式组件是什么?
简单介绍一下声明式组件与函数式组件,大多数时候我们引入组件都采用声明式的的方式,这里以 Vant 组件库为例,类似 Button 按钮这种就是声明式组件:
<van-button type="primary">主要按钮</van-button>
还有类似
<TheWelcome />
这种自定义名称且在 .vue 文件里引用其他 .vue 文件的就是声明式组件
<template>
<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>
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>
</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样式添加到全局上