基于Ant-design-vue的Modal彈窗 封裝 命令式與Hooks用法
前言
通常大家在使用彈窗有多樣化的使用方式,常見(jiàn)的是直接使用該 Modal 組件,然后顯隱的狀態(tài)放在父容器里面維護(hù)。

其次就是在全局掛載一個(gè)公共的彈窗組件,然后通過(guò) store 來(lái)傳遞不同的參數(shù),并且通過(guò) store 中的方法來(lái)改變 state.visble 的狀態(tài),從而使得彈窗展示。

雖然說(shuō)這些種方式可以實(shí)現(xiàn)同等功能,但總覺(jué)得用的不是很直接, 邏輯片段寫的也很分散, 后期代碼量越多維護(hù)成本越高,那有沒(méi)有一種比較好的思路處理這個(gè)問(wèn)題呢?將邏片段進(jìn)行統(tǒng)一管理。
下面我逐一說(shuō)一下正常開(kāi)發(fā)中 model 彈窗的組件和我自己研究出來(lái)的使用方式,在 命令式 跟 hooks 兩種方式中可以完全的解決代碼邏輯分散問(wèn)題。
組件式-用法
使用方式
<template>
<Modal v-model:visible="visible" title="彈窗"> ...content </Modal>
<Button type="primary" @click="visible = true">打開(kāi)彈窗</Button>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { Modal, Button } from "ant-design-vue";
export default defineComponent({
components: {
Modal,
Button,
},
setup() {
const visible = ref(false);
return { visible };
},
});
</script>組件式用法也可以說(shuō)是傳統(tǒng)用法實(shí)際就是把市面上一些封裝好的ui組件導(dǎo)入進(jìn)來(lái)使用,或者是自己封裝的組件。這樣使用確實(shí)很方便也很快捷。
比如說(shuō)現(xiàn)在有一個(gè)需求,在當(dāng)前頁(yè)面中有 2 按鈕需要打開(kāi) 2 個(gè)不同的彈窗。此時(shí)我們需要在當(dāng)前組件中聲明 2 個(gè) visible 初始值為 ref(false) 的變量,這還僅僅是控制一個(gè)顯隱的功能,那么隨著業(yè)務(wù)的復(fù)雜度增加,當(dāng)前頁(yè)面需要打開(kāi)彈窗的按鈕增加,那么當(dāng)前組件就會(huì)變得越來(lái)越復(fù)雜,維護(hù)成本隨之也會(huì)增高。很有可能一個(gè)邏輯清晰的組件就會(huì)變得如下代碼一樣。
<template>
<Modal v-model:visible="editVisible" title="彈窗">
<Form>
<FormItem label="名稱">
<Input v-memo="editFormData.name" placeholder="請(qǐng)輸入" />
</FormItem>
</Form>
</Modal>
<Button type="primary" @click="editVisible = true">修改數(shù)據(jù)</Button>
<Modal v-model:visible="detailVisible" title="彈窗"> ...detailContent </Modal>
<Button type="primary" @click="detailVisible = true">查看詳情</Button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
import { Modal, Button, Form, FormItem, Input } from "ant-design-vue";
export default defineComponent({
components: {
Modal,
Button,
Form,
FormItem,
Input,
},
setup() {
const editVisible = ref(false);
const detailVisible = ref(false);
const editFormData = reactive({ name: "" });
return { editVisible, detailVisible, editFormData };
},
});
</script>隨隨便便增加一點(diǎn)業(yè)務(wù)邏輯兩個(gè)彈窗組件的數(shù)據(jù)就會(huì)混在一起,當(dāng)然我們可以把彈窗渲染的一部分內(nèi)容抽離出來(lái),這樣也不能排除你增加按鈕的邏輯。
命令式-用法
使用方式
<template>
<Button @click="openAModal"></Button>
</template>
<script>
import { Button } from "ant-design-vue";
import openAModal from "./openAModal";
export default defineComponent({
components: {
Button,
},
setup() {
return {
openAModal,
};
},
});
</script>通過(guò)以上代碼,可以看出命令式使用方式要比組件式 使用方式簡(jiǎn)單的多,直接調(diào)用一個(gè)方法就可以實(shí)現(xiàn)同等等功能,非常的快捷方便,后期需要修改業(yè)務(wù)路基即可一針見(jiàn)血的直接奔向這個(gè)方法來(lái)做修改就好,相比組件式用法也更易于維護(hù)。
使用命令式用法,我們需要?jiǎng)?chuàng)建一個(gè)工具函數(shù)來(lái)包裝一下 Modal。下面的代碼是針對(duì) ant-design-vue@2.x 做的封裝這部分的邏輯我就不分析了,主要是看如何使用這個(gè)命令式的調(diào)用方法,感興趣的同仁們,可以自行研究。當(dāng)然下面代碼也針對(duì)于 ant 的 Modal 特殊優(yōu)化了一下,用過(guò)的人可能知道 使用Ant Modal如果對(duì) onOk 事件不做處理的話他是無(wú)法主動(dòng)關(guān)閉彈窗的。而且 ant Modal 的 okText、cancelText 一定要給一個(gè)名稱 不然他默認(rèn)第一次顯示的是中文,關(guān)閉后再次打開(kāi)卻是英文。
核心實(shí)現(xiàn)邏輯
// modal.js
import { createVNode, render as vueRender } from "vue";
import { Modal as AntModal } from "ant-design-vue";
/**
* @param {import("vue").DefineComponent} content
* @param {import("vue").Prop} props
* @param {Omit<import("ant-design-vue").ModalProps, 'visible'>} config
*/
export default function modal(content, props, config) {
const container = document.createDocumentFragment();
const _contentVnode = createVNode(content, props);
const metadata = Object.create({
okText: "確定",
cancelText: "取消",
visible: true,
...config,
});
metadata.onCancel = async function (...arg) {
await config.onCancel?.(...arg);
close();
};
metadata.onOk = async function () {
if (!(config.onOk instanceof Function)) {
close();
return;
}
const result = config.onOk();
if (!(result instanceof Promise)) {
close();
return;
}
update({ confirmLoading: true });
return result
.then(() => {
update({ confirmLoading: false });
close();
})
.catch(() => {
update({ confirmLoading: false });
});
};
let vm = createVNode(AntModal, metadata, () => _contentVnode);
/**
*
* @param {typeof config} config
*/
function update(config) {
Object.assign(vm.component.props, config);
vm.component.update();
}
function close() {
metadata.visible = false;
update(metadata);
}
function destroy() {
if (vm) {
vueRender(null, container);
vm = null;
}
}
/** 渲染 */
vueRender(vm, container);
return {
..._contentVnode,
close,
destroy,
update,
};
}主要工具實(shí)現(xiàn)完了后,現(xiàn)在假設(shè)有一個(gè) A.vue 的組件,需要在 modal 彈窗中渲染,并且支持一個(gè)可配的 title ,通常我們的寫法是直接在 template中寫入 <Modal> <A title="名稱" /> </Modal>,但這次我們不這么做,我們先寫一個(gè) A.vue的組件 代碼如下:
// A.vue
<template>
<h1>我是A組件</h1>
<div>{{ title }}</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
props: {
title: {
type: String,
default: "",
},
},
});
</script>現(xiàn)在基于 A.vue 來(lái)聲明一個(gè)調(diào)用彈窗的方法,因?yàn)闆](méi)有業(yè)務(wù)場(chǎng)景 所以下面的方法簡(jiǎn)單點(diǎn)沒(méi)有去設(shè)置形參,如果后面業(yè)務(wù)有需求可以傳遞參數(shù)來(lái)使用。代碼如下:
// openAModal.js
import A from './A.vue'
import modal from './modal.js'
export default function openAModal() {
modal(A, {title: 'A組件的props'}, {...彈窗的配置項(xiàng)})
}后面使用直接調(diào)用 openAModal 即可在彈窗中展現(xiàn) A.vue。這樣在使用的時(shí)候無(wú)需在意 template 結(jié)構(gòu),更不用去聲明 visible 的屬性,彈窗的邏輯也不會(huì)與業(yè)務(wù)混合在一起,后期也方便維護(hù),完全符合設(shè)計(jì)原則中的單一職責(zé)。
當(dāng)然這樣也會(huì)有缺陷,因?yàn)閺棿敖M件是使用 render 函數(shù)渲染的,所以在modal 中的組件無(wú)法獲取到 vue 實(shí)例中全局注冊(cè)的一些方法,比如你全局 app.use(router),那么你在 A.vue 中是無(wú)法通過(guò) useRouter() 來(lái)獲取路由實(shí)例。
通常這樣打開(kāi)彈窗,組件內(nèi)使用的參數(shù)建議以傳遞參數(shù)的形式,不要全局獲取。 如果你非要在組件中使用 useRouter 、useStore 等一些全局注冊(cè)的工具,請(qǐng)繼續(xù)閱讀下面的 hooks 用法,該方法模擬了 arco-design-react的 useModal 用法,相對(duì)命令式封裝做的事情要少的多。
Hooks-用法
使用方式
<template>
<component :is="contextHolder" />
<Button @click="openAModal">打開(kāi)彈窗</Button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { Button } from "ant-design-vue";
import useModal from "./useModal.js";
import A from "./A.vue";
export default defineComponent({
components: {
Button,
},
setup() {
const [modal, contextHolder] = useModal({ title: "彈窗", content: A });
return {
openAModal: modal.show,
contextHolder,
};
},
});
</script>通過(guò)以上使用示例,使用方式類似于命令式,也是調(diào)用api去操作彈窗。但是比 命令式 多了一個(gè) contextHolder,為了處理 modal 不脫離當(dāng)前 vue 的實(shí)例,直接借助于vue component 組件去渲染當(dāng)前我們自己定義的 Component。這樣一來(lái)完美的解決了 命令式 調(diào)用方法的缺陷問(wèn)題。
hook 的使用方式,應(yīng)該也可說(shuō)是一種高級(jí)封裝組件的方式,需要傳遞一個(gè)你要渲染的組件然后,暴露一個(gè) contextHolder 和一些 Api 即可, 然后操作當(dāng)前hooks的 Api 來(lái)控制 modal。
核心實(shí)現(xiàn)邏輯
// useModal
import { createVNode, ref, defineComponent } from "vue";
import { Modal as AntModal } from "ant-design-vue";
/**
* @typedef {ReturnType<typeof defineComponent>} Component
* @typedef {Omit<import("ant-design-vue").ModalProps, 'visible'>} ModalProps
* @param {Omit<import("ant-design-vue").ModalProps, 'visible'> & {content: Component}} props
*
* @returns {[{show:() => void}, Component]}
*/
export default function useModal(props = {}) {
const visible = ref(false);
// 定義需要渲染的組件即Modal
const contextHolder = defineComponent({
render() {
return createVNode(AntModal, { ...props, visible: visible.value }, () =>
createVNode(props.content)
);
},
});
const show = () => {
visible.value = true;
};
return [{ show }, contextHolder];
}
}上面簡(jiǎn)單的實(shí)現(xiàn)了一個(gè) useModal ,使用 jsDoc 定義了一下入?yún)⒑头祷貐?shù),可以看到出來(lái)入?yún)⒃诨?modal的props基礎(chǔ)之上增加了一個(gè)content 屬性,就是用來(lái)接受你要渲染不同的組件的。
在 useModal 簡(jiǎn)單的暴露了一個(gè) show 方法,還有一些方法大家都可以自由發(fā)揮的,比如說(shuō),傳入的組件怎么怎么傳參數(shù),傳入的組件props變更怎么去更新,點(diǎn)擊 onOk 的時(shí)候怎么獲取到傳入組件的里面的數(shù)據(jù)。這些都是可以通過(guò)方法去實(shí)現(xiàn)的,一些邊界問(wèn)題我就沒(méi)有去細(xì)處理了。主要能夠了解這種寫法的思路。有思路之后,就可以隨意改造。并且還能保留原有的類型提示。讓你在使用與排錯(cuò)的時(shí)候不會(huì)迷路。
總結(jié)
相對(duì)于正常開(kāi)發(fā)我們使用了 vue 的 render 、component 的 is 、createVnode,借助了 arco-design-react 封裝的 useModal, ant-design-vue 的 Modal.confirm 思路來(lái)自動(dòng)動(dòng)手改造了一版,vue 版本的 useModal 與 命令式 的使用方法。當(dāng)然如果能夠在vue中熟練使用 jsx 語(yǔ)法, 那基于這個(gè)封裝簡(jiǎn)直是再簡(jiǎn)單不過(guò)了。
最后感謝大家的閱讀,希望本文對(duì)你在前端開(kāi)發(fā)的學(xué)習(xí)和實(shí)踐中有所幫助。繼續(xù)保持好奇心,追求卓越,享受前端開(kāi)發(fā)的旅程!
以上就是基于Ant-design-vue的Modal彈窗封裝命令式與Hooks用法的詳細(xì)內(nèi)容,更多關(guān)于Ant-design-vue彈窗封裝與Hooks用法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何在Vue單頁(yè)面中進(jìn)行業(yè)務(wù)數(shù)據(jù)的上報(bào)
為什么要在標(biāo)題里加上一個(gè)業(yè)務(wù)數(shù)據(jù)的上報(bào)呢,因?yàn)樵谠蹅兦岸隧?xiàng)目中,可上報(bào)的數(shù)據(jù)維度太多,比如還有性能數(shù)據(jù)、頁(yè)面錯(cuò)誤數(shù)據(jù)、console捕獲等。這里我們只講解業(yè)務(wù)數(shù)據(jù)的埋點(diǎn)。2021-05-05
Vue+express+Socket實(shí)現(xiàn)聊天功能
這篇文章主要為大家詳細(xì)介紹了Vue+express+Socket實(shí)現(xiàn)聊天功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
前端vue3框架搭建及創(chuàng)建項(xiàng)目工程完整步驟(小白版)
很多vue初學(xué)者一開(kāi)始就在追求如何做出很炫酷的效果,而對(duì)頁(yè)面的整體布局沒(méi)有一個(gè)比較全面的了解,這篇文章主要介紹了前端vue3框架搭建及創(chuàng)建項(xiàng)目工程的相關(guān)資料,需要的朋友可以參考下2025-07-07
vue3?element-plus?實(shí)現(xiàn)表格數(shù)據(jù)更改功能詳細(xì)步驟
這篇文章主要介紹了vue3 element-plus實(shí)現(xiàn)表格數(shù)據(jù)更改功能,本文分步驟結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
Vue toRef toRefs toRaw函數(shù)使用示例
這篇文章主要介紹了Vue toRef toRefs toRaw函數(shù)使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-03-03

