用vue3封裝一個(gè)符合思維且簡單實(shí)用的彈出層
前言
在平常開發(fā)中,彈出層算是一個(gè)最常用的組件了,尤其是后臺的表單頁,詳情頁,用戶端的各種確認(rèn)都很適合使用彈出層組件展示,但是一般組件庫提供給我們的一般還是組件的形式,或者是一個(gè)簡單的服務(wù)。
組件形式的彈出層,在我看來應(yīng)該是組件庫提供給我們二次封裝用的,如果直接其實(shí)很不符合直覺
寫在頁面結(jié)構(gòu)里,但是卻不是在頁面結(jié)構(gòu)中展示,放在那個(gè)位置都不合適只能放在最下邊
一個(gè)頁面如果只有一個(gè)彈出層還好維護(hù),多幾個(gè)先不說放在那里,光維護(hù)彈出層的展示隱藏變量都是件頭大的事情
彈出層中間展示的如果是一個(gè)表單或者一個(gè)業(yè)務(wù)很重的頁面,邏輯就會跟頁面混在一起不好維護(hù),如果抽離成組件,在后臺這種全是表格表單的時(shí)候,都抽離成組件太過麻煩
那么有沒有更符合思維的方式使用彈窗呢,嘿嘿還真有,那就是服務(wù)創(chuàng)建彈出層
服務(wù)式彈出層
等等!如果是服務(wù)創(chuàng)建彈出層每個(gè)ui組件庫基本都提供了,為什么還要封裝呢?因?yàn)榻M件庫提供的服務(wù)一般都是用于簡單的確認(rèn)彈窗,如果是更重的表單彈窗就難以用組件庫提供的服務(wù)創(chuàng)建了,我們以ant-design-vue
的modal為例子看看。
<template> <a-button @click="showConfirm">Confirm</a-button> </template> <script lang="ts"> import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; import { createVNode, defineComponent } from 'vue'; import { Modal } from 'ant-design-vue'; export default defineComponent({ setup() { const showConfirm = () => { Modal.confirm({ title: 'Do you want to delete these items?', icon: createVNode(ExclamationCircleOutlined), content: 'When clicked the OK button, this dialog will be closed after 1 second', onOk() { return new Promise((resolve, reject) => { setTimeout(Math.random() > 0.5 ? resolve : reject, 1000); }).catch(() => console.log('Oops errors!')); }, // eslint-disable-next-line @typescript-eslint/no-empty-function onCancel() {}, }); }; return { showConfirm, }; }, }); </script>
可以看到modal提供了屬性content
,在文檔里我們可以看到他的類型是可以傳vue組件,但是這樣寫是有弊端的,我們無法在content
中的組件關(guān)閉modal,想要在子組件關(guān)閉Modal需要把Modal本身傳遞給子組件,然后觸發(fā)destroy();
。
顯然modal中是一個(gè)表單和重業(yè)務(wù)的組件時(shí),是很難支持我們的工作的,一是沒法簡單直接的在子組件關(guān)閉彈出層,二是content
中的組件傳遞值給父組件使用也比較麻煩。
用Promise來創(chuàng)建吧!
用promise
來創(chuàng)建,我們通過在close時(shí)觸發(fā)resolve,還可以通過resolve傳值,來觸發(fā)then,這樣非常符合邏輯和語意。
事不宜遲我們以element-plus
的dialog
為例子,來看看如何用Promise
封裝彈出層。
// useDialog.ts import { createApp, createVNode, defineComponent, h, ref, onUnmounted, } from "vue"; import { ElDialog } from "element-plus"; import type { App, Component, ComputedOptions, MethodOptions } from "vue"; //引入dialog的類型 import type { DialogProps } from "element-plus"; export type OverlayType = { component: Component<any, any, any, ComputedOptions, MethodOptions>; options?: Partial<DialogProps>; params?: any; }; export class OverlayService { //overlay的vue實(shí)例 private OverlayInstance!: App; // ui庫的組件一般都帶有動畫效果,所以需要維護(hù)一個(gè)布爾值,來做展示隱藏 public show = ref<boolean>(false); // 組件庫的options private options: Partial<DialogProps> = {}; //在open中傳遞給子組件的參數(shù) private params: any = {}; //掛載的dom public overlayElement!: Element | null; //子組件 private childrenComponent!: Component< any, any, any, ComputedOptions, MethodOptions >; //close觸發(fā)的resolve,先由open創(chuàng)建賦予 private _resolve: (value?: unknown) => void = () => {}; private _reject: (reason?: any) => void = () => {}; constructor() { this.overlayElement = document.createElement("div"); document.body.appendChild(this.overlayElement); onUnmounted(() => { //離開頁面時(shí)卸載overlay vue實(shí)例 this.OverlayInstance?.unmount(); if (this.overlayElement?.parentNode) { this.overlayElement.parentNode.removeChild(this.overlayElement); } this.overlayElement = null; }); } private createdOverlay() { const vm = defineComponent(() => { return () => h( ElDialog, { //默認(rèn)在彈窗關(guān)閉時(shí)銷毀子組件 destroyOnClose: true, ...this.options, modelValue: this.show.value, onClose: this.close.bind(this), }, { default: () => createVNode(this.childrenComponent, { close: this.close.bind(this), params: this.params, }), } ); }); if (this.overlayElement) { this.OverlayInstance = createApp(vm); this.OverlayInstance.mount(this.overlayElement); } } //打開彈窗的方法 返回promsie public open(overlay: OverlayType) { const { component, params, options } = overlay; this.childrenComponent = component; this.params = params; if (options) { this.options = options; } return new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; //判斷是否有overlay 實(shí)例 if (!this.OverlayInstance) { this.createdOverlay(); } this.show.value = true; }); } //彈窗的關(guān)閉方法,可以傳參觸發(fā)open的promise下一步 public close(msg?: any) { if (!this.overlayElement) return; this.show.value = false; if (msg) { this._resolve(msg); } else { this._resolve(); } } } //創(chuàng)建一個(gè)hooks 好在setup中使用 export const useDialog = () => { const overlayService = new OverlayService(); return { open: overlayService.open.bind(overlayService), close: overlayService.close.bind(overlayService), }; };
封裝好dialog服務(wù)之后,現(xiàn)在我們先創(chuàng)建一個(gè)子組件,傳遞給open的子組件會接受到close,params兩個(gè)props
<!--ChildDemo.vue --> <template> <div> {{params}} <button @click="close('關(guān)閉了彈窗')" >關(guān)閉彈窗</button> </div> </template> <script lang="ts" setup> const props = defineProps<{ close: (msg?: any) => void; params: any; }>(); </script>
然后我們在頁面使用open
<template> <div> <button @click="openDemo" >打開彈窗</button> </div> </template> <script lang="ts" setup> import ChildDemo from './ChildDemo.vue' import { useDialog } from "./useDialog"; const { open } = useDialog(); const openDemo = () => { open({ component: ChildDemo, options: { title: "彈窗demo" }, params:{abc:'1'} }).then((msg)=>{ console.log('關(guān)閉彈窗觸發(fā)',msg) }); }; </script>
好了到此我們就封裝了一個(gè)簡單實(shí)用的彈窗服務(wù)。
寫在后頭
其實(shí)這樣封裝的還是有一個(gè)小問題,那就是沒法拿到vue實(shí)例,只能拿到overlay的實(shí)例,因?yàn)閛verlay是重新創(chuàng)建的vue實(shí)例,所以不要使用全局注冊的組件,在子組件上單獨(dú)引入,pinia
,vuex
,router
這些當(dāng)params
做傳入子組件。
如果不想自己封裝,可以用我寫的庫vdi useOverlay hook搭配任意的ui組件庫,vdi還提供了更好的依賴注入嗷。如果搭配vdi的vueModule使用,就沒有上面說的問題了
到此這篇關(guān)于用vue3封裝一個(gè)符合思維且簡單實(shí)用的彈出層的文章就介紹到這了,更多相關(guān)vue3封裝彈出層內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue監(jiān)聽頁面中的某個(gè)div的滾動事件并判斷滾動的位置
本文主要介紹了vue監(jiān)聽頁面中的某個(gè)div的滾動事件并判斷滾動的位置,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03vue如何通過點(diǎn)擊事件實(shí)現(xiàn)頁面跳轉(zhuǎn)詳解
頁面跳轉(zhuǎn),我們一般都通過路由跳轉(zhuǎn)實(shí)現(xiàn),通常情況下可直接使用router-link標(biāo)簽實(shí)現(xiàn)頁面跳轉(zhuǎn),下面這篇文章主要給大家介紹了關(guān)于vue如何通過點(diǎn)擊事件實(shí)現(xiàn)頁面跳轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下2022-07-07