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

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

雖然說這些種方式可以實現(xiàn)同等功能,但總覺得用的不是很直接, 邏輯片段寫的也很分散, 后期代碼量越多維護(hù)成本越高,那有沒有一種比較好的思路處理這個問題呢?將邏片段進(jìn)行統(tǒng)一管理。
下面我逐一說一下正常開發(fā)中 model 彈窗的組件和我自己研究出來的使用方式,在 命令式 跟 hooks 兩種方式中可以完全的解決代碼邏輯分散問題。
組件式-用法
使用方式
<template>
<Modal v-model:visible="visible" title="彈窗"> ...content </Modal>
<Button type="primary" @click="visible = true">打開彈窗</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>組件式用法也可以說是傳統(tǒng)用法實際就是把市面上一些封裝好的ui組件導(dǎo)入進(jìn)來使用,或者是自己封裝的組件。這樣使用確實很方便也很快捷。
比如說現(xiàn)在有一個需求,在當(dāng)前頁面中有 2 按鈕需要打開 2 個不同的彈窗。此時我們需要在當(dāng)前組件中聲明 2 個 visible 初始值為 ref(false) 的變量,這還僅僅是控制一個顯隱的功能,那么隨著業(yè)務(wù)的復(fù)雜度增加,當(dāng)前頁面需要打開彈窗的按鈕增加,那么當(dāng)前組件就會變得越來越復(fù)雜,維護(hù)成本隨之也會增高。很有可能一個邏輯清晰的組件就會變得如下代碼一樣。
<template>
<Modal v-model:visible="editVisible" title="彈窗">
<Form>
<FormItem label="名稱">
<Input v-memo="editFormData.name" placeholder="請輸入" />
</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>隨隨便便增加一點業(yè)務(wù)邏輯兩個彈窗組件的數(shù)據(jù)就會混在一起,當(dāng)然我們可以把彈窗渲染的一部分內(nè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>通過以上代碼,可以看出命令式使用方式要比組件式 使用方式簡單的多,直接調(diào)用一個方法就可以實現(xiàn)同等等功能,非常的快捷方便,后期需要修改業(yè)務(wù)路基即可一針見血的直接奔向這個方法來做修改就好,相比組件式用法也更易于維護(hù)。
使用命令式用法,我們需要創(chuàng)建一個工具函數(shù)來包裝一下 Modal。下面的代碼是針對 ant-design-vue@2.x 做的封裝這部分的邏輯我就不分析了,主要是看如何使用這個命令式的調(diào)用方法,感興趣的同仁們,可以自行研究。當(dāng)然下面代碼也針對于 ant 的 Modal 特殊優(yōu)化了一下,用過的人可能知道 使用Ant Modal如果對 onOk 事件不做處理的話他是無法主動關(guān)閉彈窗的。而且 ant Modal 的 okText、cancelText 一定要給一個名稱 不然他默認(rèn)第一次顯示的是中文,關(guān)閉后再次打開卻是英文。
核心實現(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,
};
}主要工具實現(xiàn)完了后,現(xiàn)在假設(shè)有一個 A.vue 的組件,需要在 modal 彈窗中渲染,并且支持一個可配的 title ,通常我們的寫法是直接在 template中寫入 <Modal> <A title="名稱" /> </Modal>,但這次我們不這么做,我們先寫一個 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 來聲明一個調(diào)用彈窗的方法,因為沒有業(yè)務(wù)場景 所以下面的方法簡單點沒有去設(shè)置形參,如果后面業(yè)務(wù)有需求可以傳遞參數(shù)來使用。代碼如下:
// openAModal.js
import A from './A.vue'
import modal from './modal.js'
export default function openAModal() {
modal(A, {title: 'A組件的props'}, {...彈窗的配置項})
}后面使用直接調(diào)用 openAModal 即可在彈窗中展現(xiàn) A.vue。這樣在使用的時候無需在意 template 結(jié)構(gòu),更不用去聲明 visible 的屬性,彈窗的邏輯也不會與業(yè)務(wù)混合在一起,后期也方便維護(hù),完全符合設(shè)計原則中的單一職責(zé)。
當(dāng)然這樣也會有缺陷,因為彈窗組件是使用 render 函數(shù)渲染的,所以在modal 中的組件無法獲取到 vue 實例中全局注冊的一些方法,比如你全局 app.use(router),那么你在 A.vue 中是無法通過 useRouter() 來獲取路由實例。
通常這樣打開彈窗,組件內(nèi)使用的參數(shù)建議以傳遞參數(shù)的形式,不要全局獲取。 如果你非要在組件中使用 useRouter 、useStore 等一些全局注冊的工具,請繼續(xù)閱讀下面的 hooks 用法,該方法模擬了 arco-design-react的 useModal 用法,相對命令式封裝做的事情要少的多。
Hooks-用法
使用方式
<template>
<component :is="contextHolder" />
<Button @click="openAModal">打開彈窗</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>通過以上使用示例,使用方式類似于命令式,也是調(diào)用api去操作彈窗。但是比 命令式 多了一個 contextHolder,為了處理 modal 不脫離當(dāng)前 vue 的實例,直接借助于vue component 組件去渲染當(dāng)前我們自己定義的 Component。這樣一來完美的解決了 命令式 調(diào)用方法的缺陷問題。
hook 的使用方式,應(yīng)該也可說是一種高級封裝組件的方式,需要傳遞一個你要渲染的組件然后,暴露一個 contextHolder 和一些 Api 即可, 然后操作當(dāng)前hooks的 Api 來控制 modal。
核心實現(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];
}
}上面簡單的實現(xiàn)了一個 useModal ,使用 jsDoc 定義了一下入?yún)⒑头祷貐?shù),可以看到出來入?yún)⒃诨?modal的props基礎(chǔ)之上增加了一個content 屬性,就是用來接受你要渲染不同的組件的。
在 useModal 簡單的暴露了一個 show 方法,還有一些方法大家都可以自由發(fā)揮的,比如說,傳入的組件怎么怎么傳參數(shù),傳入的組件props變更怎么去更新,點擊 onOk 的時候怎么獲取到傳入組件的里面的數(shù)據(jù)。這些都是可以通過方法去實現(xiàn)的,一些邊界問題我就沒有去細(xì)處理了。主要能夠了解這種寫法的思路。有思路之后,就可以隨意改造。并且還能保留原有的類型提示。讓你在使用與排錯的時候不會迷路。
總結(jié)
相對于正常開發(fā)我們使用了 vue 的 render 、component 的 is 、createVnode,借助了 arco-design-react 封裝的 useModal, ant-design-vue 的 Modal.confirm 思路來自動動手改造了一版,vue 版本的 useModal 與 命令式 的使用方法。當(dāng)然如果能夠在vue中熟練使用 jsx 語法, 那基于這個封裝簡直是再簡單不過了。
最后感謝大家的閱讀,希望本文對你在前端開發(fā)的學(xué)習(xí)和實踐中有所幫助。繼續(xù)保持好奇心,追求卓越,享受前端開發(fā)的旅程!
以上就是基于Ant-design-vue的Modal彈窗封裝命令式與Hooks用法的詳細(xì)內(nèi)容,更多關(guān)于Ant-design-vue彈窗封裝與Hooks用法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入學(xué)習(xí)Vue nextTick的用法及原理
這篇文章主要介紹了深入學(xué)習(xí)Vue nextTick的用法及原理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10
Element中Upload組件上傳功能實現(xiàn)(圖片和文件的默認(rèn)上傳及自定義上傳)
這篇文章主要介紹了Element中Upload組件上傳功能實現(xiàn)包括圖片和文件的默認(rèn)上傳及自定義上傳,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-01-01
vue中計算屬性computed理解說明包括vue偵聽器,緩存與computed的區(qū)別
這篇文章主要介紹了對vue中計算屬性computed理解說明包括vue偵聽器,緩存與computed的區(qū)別,需要的朋友可以參考下2022-05-05
Vue 設(shè)置axios請求格式為form-data的操作步驟
今天小編就為大家分享一篇Vue 設(shè)置axios請求格式為form-data的操作步驟,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-10-10

