用vue3封裝一個符合思維且簡單實用的彈出層
前言
在平常開發(fā)中,彈出層算是一個最常用的組件了,尤其是后臺的表單頁,詳情頁,用戶端的各種確認都很適合使用彈出層組件展示,但是一般組件庫提供給我們的一般還是組件的形式,或者是一個簡單的服務(wù)。
組件形式的彈出層,在我看來應(yīng)該是組件庫提供給我們二次封裝用的,如果直接其實很不符合直覺
寫在頁面結(jié)構(gòu)里,但是卻不是在頁面結(jié)構(gòu)中展示,放在那個位置都不合適只能放在最下邊
一個頁面如果只有一個彈出層還好維護,多幾個先不說放在那里,光維護彈出層的展示隱藏變量都是件頭大的事情
彈出層中間展示的如果是一個表單或者一個業(yè)務(wù)很重的頁面,邏輯就會跟頁面混在一起不好維護,如果抽離成組件,在后臺這種全是表格表單的時候,都抽離成組件太過麻煩
那么有沒有更符合思維的方式使用彈窗呢,嘿嘿還真有,那就是服務(wù)創(chuàng)建彈出層
服務(wù)式彈出層
等等!如果是服務(wù)創(chuàng)建彈出層每個ui組件庫基本都提供了,為什么還要封裝呢?因為組件庫提供的服務(wù)一般都是用于簡單的確認彈窗,如果是更重的表單彈窗就難以用組件庫提供的服務(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中是一個表單和重業(yè)務(wù)的組件時,是很難支持我們的工作的,一是沒法簡單直接的在子組件關(guān)閉彈出層,二是content中的組件傳遞值給父組件使用也比較麻煩。
用Promise來創(chuàng)建吧!
用promise來創(chuàng)建,我們通過在close時觸發(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實例
private OverlayInstance!: App;
// ui庫的組件一般都帶有動畫效果,所以需要維護一個布爾值,來做展示隱藏
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(() => {
//離開頁面時卸載overlay vue實例
this.OverlayInstance?.unmount();
if (this.overlayElement?.parentNode) {
this.overlayElement.parentNode.removeChild(this.overlayElement);
}
this.overlayElement = null;
});
}
private createdOverlay() {
const vm = defineComponent(() => {
return () =>
h(
ElDialog,
{
//默認在彈窗關(guān)閉時銷毀子組件
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 實例
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)建一個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)建一個子組件,傳遞給open的子組件會接受到close,params兩個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>好了到此我們就封裝了一個簡單實用的彈窗服務(wù)。
寫在后頭
其實這樣封裝的還是有一個小問題,那就是沒法拿到vue實例,只能拿到overlay的實例,因為overlay是重新創(chuàng)建的vue實例,所以不要使用全局注冊的組件,在子組件上單獨引入,pinia,vuex,router這些當(dāng)params做傳入子組件。
如果不想自己封裝,可以用我寫的庫vdi useOverlay hook搭配任意的ui組件庫,vdi還提供了更好的依賴注入嗷。如果搭配vdi的vueModule使用,就沒有上面說的問題了
到此這篇關(guān)于用vue3封裝一個符合思維且簡單實用的彈出層的文章就介紹到這了,更多相關(guān)vue3封裝彈出層內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue監(jiān)聽頁面中的某個div的滾動事件并判斷滾動的位置
本文主要介紹了vue監(jiān)聽頁面中的某個div的滾動事件并判斷滾動的位置,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
vue如何通過點擊事件實現(xiàn)頁面跳轉(zhuǎn)詳解
頁面跳轉(zhuǎn),我們一般都通過路由跳轉(zhuǎn)實現(xiàn),通常情況下可直接使用router-link標簽實現(xiàn)頁面跳轉(zhuǎn),下面這篇文章主要給大家介紹了關(guān)于vue如何通過點擊事件實現(xiàn)頁面跳轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下2022-07-07

