Vue3封裝全局函數(shù)式組件方法總結(jié)
前言
相信大家在 Vue 中考慮復(fù)用邏輯的時候經(jīng)常使用組件化開發(fā),也肯定使用過函數(shù)式組件,就是那種在 js 中也能夠?qū)胝{(diào)用的組件。那么如何去封裝這么一個函數(shù)式組件呢,這篇文章將采用Toast組件簡單介紹一下封裝的方法,封裝之后就能大大提高我們開發(fā)的效率了。
一、函數(shù)式組件是什么?
簡單介紹一下聲明式組件與函數(shù)式組件,大多數(shù)時候我們引入組件都采用聲明式的的方式,這里以 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>而函數(shù)式組件則是通過調(diào)用 API 的方式快速喚起全局的組件,還是以 Vant 組件庫為例,比如使用 Toast 組件,調(diào)用函數(shù)后會直接在頁面中渲染對應(yīng)的輕提示:
import { showToast } from 'vant';
showToast('提示內(nèi)容');通常我們使用函數(shù)式組件是在某個交互完成時觸發(fā),又或者是在非.vue文件里喚起全局的組件,例如封裝axios,在axios.js中使用Toast組件顯示報錯信息:
showToast('服務(wù)器響應(yīng)超時,請刷新當前頁');二、創(chuàng)建一個函數(shù)式組件
下面將創(chuàng)建一個自己定義的toast組件,由于這個toast組件默認是顯示成功的,所以稱之為“okToast”,先展示一下調(diào)用后的效果:

1. 封裝toast組件
與創(chuàng)建聲明式組件一致,在.vue文件里定義好組件接收的參數(shù)還有組件的樣式。代碼如下(示例):
<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({
//文案內(nèi)容,默認success
content: {
type: String,
default: "success",
},
//顯示時間,默認2s,傳小于0的值不自動消失,需要手動關(guān)閉
time: {
type: Number,
default: 2000,
},
//寬度,默認310px,這里考慮傳入的寬度可以用字符串也可以用數(shù)值,所以沒有定義類型
width: {
default: 310,
},
//彈窗文案文本對齊方式,默認center
textAlign: {
type: String,
default: "center",
},
//類型,默認圖標(√),傳'warn'顯示(?。瑐髌渌祫t不顯示icon,傳'icon'不顯示文本
type: {
type: String,
default: "success",
},
//接收的函數(shù)方法
hide: {
type: Function,
default: () => {},
},
});
// 彈窗顯隱控制
const isShow = ref(false);
// 寬度控制,由于設(shè)計稿寬度是750px的寬度,這里通過計算屬性,根據(jù)設(shè)備屏幕寬度自適應(yīng)顯示彈窗的寬度
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;
};
// 彈窗關(guān)閉后等動畫結(jié)束再調(diào)用卸載邏輯
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. 創(chuàng)建應(yīng)用實例
這是最關(guān)鍵的步驟,在 Vue2 的時候封裝函數(shù)式組件使用的是 Vue.extend,利用這個基礎(chǔ)的 Vue 構(gòu)造器,能創(chuàng)建Vue子類實例,然而在 Vue3 官方刪除了這個方法,但是也提供了新的api: createApp 給我們使用,利用 createApp 就能創(chuàng)建 Vue 應(yīng)用實例了。代碼如下(示例):
import { createApp } from "vue";
import OkToast from "./okToast.vue";
const okToast = options => {
// 創(chuàng)建元素節(jié)點
const rootNode = document.createElement("div");
// 在body標簽內(nèi)部插入此元素
document.body.appendChild(rootNode);
// 創(chuàng)建應(yīng)用實例(第一個參數(shù)是根組件。第二個參數(shù)可選,它是要傳遞給根組件的 props)
const app = createApp(OkToast, {
...options,
hide() {
// 卸載已掛載的應(yīng)用實例
app.unmount();
// 刪除rootNode節(jié)點
document.body.removeChild(rootNode);
},
});
// 將應(yīng)用實例掛載到創(chuàng)建的 DOM 元素上
return app.mount(rootNode);
};
// 注冊插件app.use()會自動執(zhí)行install函數(shù)
okToast.install = app => {
// 注冊全局屬性,類似于 Vue2 的 Vue.prototype
app.config.globalProperties.$okToast = options => okToast(options).show();
};
// 定義show方法用于直接調(diào)用
okToast.show = options => okToast(options).show();
export default okToast;3. 注冊插件(可省略)
代碼如下(示例):
// main.js import okToast from './plugins/okToast/index'; app.use(okToast);
Q&A: 補充一些注釋
①:為什么采用調(diào)用函數(shù)方法的方式去控制顯隱
答:目的是為了那個顯示與消失的動畫效果,當組件創(chuàng)建后需要組件內(nèi) ”isShow“ 產(chǎn)生變化才能觸發(fā)<Transition> 的動畫效果,所以這里寫了show函數(shù)方法。
②:函數(shù)式組件的這兩個文件之間的聯(lián)系
答:簡單來說,js文件傳參數(shù)及函數(shù)給vue文件,均可在 createApp 的第二個參數(shù)中傳遞,vue文件相當于子組件,使用props的方式接收;vue文件傳值及函數(shù)給js文件,可以通過 defineExpose 方法暴露出去,js文件中在應(yīng)用實例創(chuàng)建完成后,就能拿到暴露出來的屬性及方法。
三、調(diào)用
1. 注冊插件后在.vue文件內(nèi)獲取全局方法
<script setup>
import { getCurrentInstance } from 'vue';
// 獲取當前實例,在當前實例相當于 vue2 中的 this
const { proxy }: any = getCurrentInstance();
// 最簡單的調(diào)用方式,即可出來開頭所展示的效果
proxy.$okToast();
// 傳遞自定義參數(shù),與okToast.vue文件接收的參數(shù)對應(yīng)
setTimeout(() => {
proxy.$okToast({
content: 'Hello World'
});
}, 2000);
</script>2. 可不注冊插件,在.vue或.js文件內(nèi)直接調(diào)用方法
import $okToast from "./plugs/okToast";
$okToast.show({
type: "warn",
content: "Network error,try again later",
});
四、優(yōu)化改進
上面封裝的Toast組件在創(chuàng)建多個實例的時候,它們之間是互不干擾的,不會存在組件參數(shù)異常的情況。那么實際觀察 DOM 元素我們會發(fā)現(xiàn)其在 DOM 上是存在多個的,只不過當多次調(diào)用的時候,后面的會把前面還沒消失的Toast覆蓋了,這樣效果可能不那么友好。那么就存在兩個優(yōu)化方向:一是當后續(xù)出現(xiàn)Toast的時候結(jié)束掉前面出現(xiàn)的Toast,二是調(diào)整后續(xù)Toast出現(xiàn)的位置。
1、單例模式(推薦)
先上代碼(示例):
let rootNode = null;
let app = null;
const okToast = options => {
const dom = document.body.querySelector('.my-ok-toast');
if (!dom) {
rootNode = document.createElement('div');
// 給創(chuàng)建的元素設(shè)置 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() {
// 卸載已掛載的應(yīng)用實例
if (app) {
app.unmount();
app = null;
}
// 刪除rootNode節(jié)點
if (rootNode) {
document.body.removeChild(rootNode);
rootNode = null;
}
}
});
return app.mount(rootNode);
};效果展示:

怎么去結(jié)束前面出現(xiàn)的Toast呢,我們只需要確保全局只渲染一個Toast彈窗就行,所以可以使用單例模式,單例模式即一個類只能有一個實例。類似Vant的Toast組件,其默認采用了單例模式,即同一時間只會存在一個,這種做法應(yīng)該是普遍的彈窗做法。
2、多個提示彈窗
先上代碼(示例):
// 創(chuàng)建臨時變量保存高度值
let top = 0;
const okToast = options => {
const rootNode = document.createElement('div');
// 給創(chuàng)建的元素設(shè)置 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%);
}效果展示:

這里的做法提供給大家一種思路,實際的動畫效果還有待優(yōu)化,由于本文篇幅有限所以就不展開了,以后遇到這種需求再深入探索吧。
總結(jié)
以上就是全部內(nèi)容,本文簡單介紹了 Vue3 函數(shù)式組件的封裝方法,將其以插件的方式使用app.use() 方法安裝在 Vue 上,使其作為全局功能的工具,這就是 Vue3 中邏輯復(fù)用的插件 (Plugins) 寫法。
如果此篇文章對您有幫助,歡迎您【點贊】、【收藏】!也歡迎您【評論】留下寶貴意見,共同探討一起學(xué)習(xí)~
擴展閱讀
到此這篇關(guān)于Vue3封裝全局函數(shù)式組件方法的文章就介紹到這了,更多相關(guān)Vue3封裝全局函數(shù)式組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3+vite動態(tài)加載路由,本地路由和線上路由匹配方式
這篇文章主要介紹了vue3+vite動態(tài)加載路由,本地路由和線上路由匹配方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
Vue3中watch監(jiān)聽器及源碼學(xué)習(xí)
本文主要介紹了Vue3中watch監(jiān)聽器及源碼學(xué)習(xí),Watch偵聽器在Vue3中特性進行了一些改變和優(yōu)化,下面來詳解的介紹一下基本使用,具有一定的參考價值,感興趣的可以了解一下2024-01-01
Vue3異步數(shù)據(jù)加載組件suspense的使用方法
前端開發(fā)中異步請求是非常常見的事情,比如遠程讀取圖片,調(diào)用后端接口等等,這篇文章主要給大家介紹了關(guān)于Vue3異步數(shù)據(jù)加載組件suspense的使用方法,suspense中文含義是懸念的意思,需要的朋友可以參考下2021-08-08

