vue.js前端網(wǎng)頁彈框異步行為示例分析
1. 序
網(wǎng)頁彈框是個(gè)很常見的功能,比如需要告知用戶消息的時(shí)候 (Alert),需要用戶進(jìn)行確認(rèn)的時(shí)候 (Confirm),需要用戶補(bǔ)充一點(diǎn)信息的時(shí)候 (Prompt) …… 甚至可以彈框讓用戶填寫表單 (Modal Dialog)。
彈框之后,開發(fā)者需要知道這個(gè)彈框是什么時(shí)候關(guān)閉以便進(jìn)行接下來的操作。
在比較古老的 UI 組件中,這個(gè)事情是通過事件回調(diào)來進(jìn)行的,大概長(zhǎng)這樣:
showDialog(content, title, { closed: function() { console.log("對(duì)話框已關(guān)閉"); } })
不過對(duì)話框的行為。你看,它彈出來了,但它不會(huì)阻塞后面的代碼,而且開發(fā)者并不知道什么時(shí)候關(guān)閉,因?yàn)檫@是用戶行為。既然是異步,封裝成 Promise 使用?await
?語法來調(diào)用會(huì)更舒服一些。簡(jiǎn)單的封裝大概可以這樣:
async function asyncShowDialog(content, title, options) { return new Promise(resolve => { showDialog(content, title, { ...options, closed: resolve }); }); } (async () => { await asyncShowDialog(content, title); console.log("對(duì)話框已關(guān)閉"); })();
彈框的基本的異步行為就是這么簡(jiǎn)單,就這么結(jié)束?心有不甘,再研究研究!
2. 找兩個(gè)彈框組件看看
Ant Design Vue 使用了事件的形式,點(diǎn)擊“確定”按鈕會(huì)觸發(fā)?ok
?事件,點(diǎn)擊“取消”或者右上角的關(guān)閉按鈕會(huì)觸發(fā)?cancel
?事件。
這兩個(gè)事件處理函數(shù)通過參數(shù)對(duì)象的?onOk
?和?onCancel
?屬性掛載進(jìn)去。看起來平淡無奇,但如果處理事件返回的是一個(gè) Promise 對(duì)象,點(diǎn)擊按鈕之后會(huì)出現(xiàn)加載動(dòng)畫并等待直到 Promise 對(duì)象完成之后才會(huì)關(guān)閉對(duì)話框。這種設(shè)計(jì)把異步等待動(dòng)畫組合到彈框當(dāng)中,簡(jiǎn)潔直觀,代碼寫起來也很方便。以 confirm 對(duì)話框?yàn)槔?/p>
Modal.confirm({ ... onOk() { // 點(diǎn)擊「確定」按鈕后,會(huì)顯示加載動(dòng)畫,并在一秒后關(guān)閉對(duì)話框 return new Promise(resolve => { setTimeout(resolve, 1000); }); } ... });
而 Element Plus 使用了 Promise 形式,打開對(duì)話框時(shí),并不是把確定或取消的處理函數(shù)以參數(shù)的形式傳入,而是直接返回一個(gè) Promise 對(duì)象,供開發(fā)者通過?.then()/.catch()
?或者?await
?處理。示例:
try { await ElMessageBox.confirm(...); // 按下確定按鈕在這里處理 } catch(err) { // 按下取消按鈕在這里處理 }
Element Plus 的這種處理方式,要在對(duì)話框關(guān)閉之后才能處理業(yè)務(wù)。這也是使用 Promise 的局限 —— 對(duì)于一個(gè)已經(jīng)封裝好的 Promise 對(duì)象,很難在其中插入新的邏輯。
如果使用?ElMessageBox
?的時(shí)候也想像 Ant Design 那樣在關(guān)閉前進(jìn)行一些異步操作,只能去找找看它是否提供了關(guān)閉前的處理事件。一找還真找到了,它有?beforeClose
?事件。該事件的處理函數(shù)簽名是?beforeClose(action, instance, done)
:
action
?表示按了哪個(gè)按鈕,取值可能是?"confirm"
、"cancel"
?和?"close"
(不用解釋了吧)。
instance
?是 MessageBox 實(shí)例,可以使用它來控制一些界面效果,比如
instance.confirmButtonLoading = true
?會(huì)在“確定”按鈕上顯示加載動(dòng)畫,instance.confirmButtonText
?可以用來改變按鈕文本 …… 這些操作在進(jìn)行異步等待時(shí)可以提供更好的用戶體驗(yàn)。
done
?是一個(gè)函數(shù),調(diào)用它表示?beforeClose()
?的異步處理完成,對(duì)話框現(xiàn)在可以關(guān)閉了!
所以類似 Ant Design 的處理可以這樣寫:
try { await ElMessageBox.confirm({ ... beforeClose: async (action, instance, done) => { await new Promise(resolve => setTimeout(resolve, 1000)); done(); } }); // 按下確定按鈕在這里處理 } catch(err) { // 按下取消按鈕在這里處理 }
3. 自己肝一個(gè)
分析了兩個(gè)彈框組件的行為處理,我們已經(jīng)知道,一個(gè)體驗(yàn)良好的彈框組件應(yīng)該具備如下特征:
- 提供基于 Promise 的異步控制能力(Ant Design Vue 雖然沒有提供,但是像“序”中那樣封裝一下就可以)。
- 允許在關(guān)閉前進(jìn)行一些操作,甚至是異步操作。
- 提供異步加載過程中的界面反饋,而且最好不需要開發(fā)者來控制(從這點(diǎn)來說 Ant Design 比 Element Plus 方便)。
接下來,我們自己寫一個(gè),看看是如何實(shí)現(xiàn)上述特征的。不過,既然我們主要研究的是行為而不是數(shù)據(jù)處理,所以不用 Vue 框架,直接用 DOM 操作,然后引入 jQuery 來簡(jiǎn)化 DOM 處理。
對(duì)話框的 HTML 骨架也比較簡(jiǎn)單:下面一層蒙板,上面一個(gè)固定大小的?<div>
?層,內(nèi)部再用?<div>
?劃分成標(biāo)題、內(nèi)容、操作區(qū)三塊:
<div class="dialog" id="dialogTemplate"> <div class="dialog-window"> <div class="dialog-title">對(duì)話框標(biāo)題</div> <div class="dialog-content">對(duì)話框的內(nèi)容</div> <div class="dialog-operation"> <button type="button" class="ensure-button">確定</button> <button type="button" class="cancel-button">取消</button> </div> </div> </div>
這里把它定義成一個(gè)模板,希望每次都從它克隆一個(gè) DOM 出來呈現(xiàn),關(guān)閉即毀。
樣式表的內(nèi)容較長(zhǎng),可以從后面的示例鏈接去獲取。代碼及代碼的進(jìn)化過程才是本文的重點(diǎn)。
最簡(jiǎn)單的呈現(xiàn)是利用 jQuery 克隆一個(gè)顯示出來,但顯示前一定要記得刪除掉?id
?屬性,并把它添加到?<body>
?中去:
$("#dialogTemplate").clone().removeAttr("id").appendTo("body").show();
把它封裝成一個(gè)函數(shù),并且添加對(duì)「確定」和「取消」按鈕的處理:
function showDialog(content, title) { const $dialog = $("#dialogTemplate").clone().removeAttr("id"); // 設(shè)置對(duì)話框的標(biāo)題和內(nèi)容(簡(jiǎn)單示例,所以只處理文本) $dialog.find(".dialog-title").text(title); $dialog.find(".dialog-content").text(content); // 通過事件代理(也可以不用代理)處理兩個(gè)按鈕事件 $dialog .on("click", ".ensure-button", () => { $dialog.remove(); }) .on("click", ".cancel-button", () => { $dialog.remove(); }); $dialog.appendTo("body").show(); }
彈框的基本邏輯就出來了。現(xiàn)在做兩點(diǎn)優(yōu)化:① 把?$dialog.remove()
?封裝成函數(shù),便于對(duì)關(guān)閉對(duì)話框進(jìn)行統(tǒng)一處理(代碼復(fù)用) ② 使用?.show()
?呈現(xiàn)太過生硬,改為?fadeIn(200)
;同理,應(yīng)該在?.remove()
?之前先fadeOut(200)
。
function showDialog(...) { ... const destory = () => { $dialog.fadeOut(200, () => $dialog.remove()); }; $dialog .on("click", ".ensure-button", destroy) .on("click", ".cancel-button", destroy); $dialog.appendTo("body").fadeIn(200); }
3.1. 封裝 Promise
到這一步,彈框已經(jīng)可以正常彈出/關(guān)閉了,但是沒辦法注入「確定」或「取消」的邏輯代碼。前面提到可以通過事件或 Promise 兩種形式來提供接口,這里使用 Promise 的方式。如果點(diǎn)「確定」就 resolve,點(diǎn)「取消」就 reject。
function showDialog(...) { ... const promise = new Promise((resolve, reject) => { $dialog .on("click", ".ensure-button", () => { destroy(); resolve("ok"); }) .on("click", ".cancel-button", () => { destroy(); reject("cancel"); }); }); $dialog.appendTo("body").fadeIn(200); return promise(); }
封裝好了,但有個(gè)問題:destroy()
?是個(gè)異步過程,但代碼并沒有等它結(jié)束,所以?showDialog()
?完成異步處理之后還在進(jìn)行?fadeOut()
?操作和?remove()
?操作。要解決這個(gè)問題,只能封裝?destory()
。當(dāng)然調(diào)用的時(shí)候也別忘了加?await
,而加?await
?就要把外層函數(shù)聲明為?async
:
function showDialog(...) { ... const destory = () => { return new Promise(resolve => { $dialog.fadeOut(200, () => { $dialog.remove(); resolve(); }); }); }; const promise = new Promise((resolve, reject) => { $dialog .on("click", ".ensure-button", async () => { await destroy(); resolve("ok"); }) .on("click", ".cancel-button", async () => { await destroy(); reject("cancel"); }); }); ... }
3.2. 確定時(shí)允許異步等待
不管「確定」還是「取消」都可以保持彈框顯示,進(jìn)行異步等待。但作為示例,這里只處理「確定」的情況。
這個(gè)異步等待過程要注入到從彈窗中,只能采用參數(shù)注入的形式。所以需要為?showDialog()
?添加一個(gè)?options
?參數(shù),允許注入一個(gè)處理函數(shù)給?onOk
?屬性,如果這個(gè)處理函數(shù)返回 Promise Like,就進(jìn)行異步等待。
先修改?showDialog()
?接口:
function showDialog(conent, title, options = {}) { ... }
然后再處理?$dialog.on("click", ".ensure-button", ...)?事件:
$dialog .on("click", ".ensure-button", async () => { const { onOk } = options; // 從 options 中拿到 onOk,如果它是一個(gè)函數(shù)才需要等待處理 if (typeof onOk === "function") { const r = onOk(); // 判斷 onOk() 的結(jié)果是不是一個(gè) Promise Like 對(duì)象 // 只有 Promise Like 對(duì)象才需要異步等待 if (typeof r?.then === "function") { const $button = $dialog.find(".ensure-button"); // 異步等待過程中需要給用戶一定反饋 // 這里偷懶沒有使用加載動(dòng)畫,只用文字來進(jìn)行反饋 $button.text("處理中..."); await r; // 因?yàn)樵谕瓿芍?,關(guān)閉之前有 200 毫秒的漸隱過程, // 所以把按鈕文本改為“完成”,給用戶及時(shí)反饋是有必要的 $button.text("完成"); } } await destroy(); resolve("ok"); })
現(xiàn)在這個(gè)彈框的行為基本上處理完了,調(diào)用的示例:
const result = await showDialog( "你好,這里是對(duì)話框的內(nèi)容", "打個(gè)招呼", { onOk: () => new Promise((resolve) => { setTimeout(resolve, 3000); }) } ).catch(msg => msg); // 這里把取消引起的 reject 變成 resolve,避免使用 try...catch... console.log(result === "ok" ? "按下確定" : "按下取消");
3.3. 細(xì)節(jié)完善
都有對(duì)話框了最后還用?console.log(...)
?實(shí)在有點(diǎn)不妥,直接彈框提示消息不更好?
但是現(xiàn)在的?showDialog()
?只處理了 Confirm 彈框,沒有處理 Alert 彈框 …… 問題不大,在?options
?里加個(gè)?type
?好了。如果?type
?是?"alert"
?就把「取消」按鈕干掉。
async function showDialog(content, title, options = {}) { ... if (options.type === "alert") { $dialog.find(".cancel-button").remove(); } ... }
然后,最后的?console.log(...)
?可以進(jìn)化一下:
showDialog(result === "ok" ? "按下確定" : "按下取消", "提示", { type: "alert" });
3.4. 改革
如果不喜歡在?options
?中注入處理函數(shù),還可以換個(gè)法子,在返回的 Promise 對(duì)象中注入。先在?.ensure-button
?的事件中把?const { onOk } = options
?改為?const { onOk } = promise
,也就是從?promise
?中獲取注入的?onOk
。然后改調(diào)用部分:
const dialog = showDialog("你好,這里是對(duì)話框的內(nèi)容", "打個(gè)招呼"); // 把處理函數(shù)注入到 promise 的 onOk dialog.onOk = () => new Promise((resolve) => { setTimeout(resolve, 3000); }); const result = await dialog.catch(msg => msg); showDialog(result === "ok" ? "按下確定" : "按下取消", "提示", { type: "alert" });
這里有幾點(diǎn)要注意:
dialog
?必須只能是?showDialog()
?直接返回的。如果調(diào)用了?.catch()
?將會(huì)得到另一個(gè) Promise 對(duì)象,此時(shí)再注入?onOk
?就注入不到?showDialog()
?里面產(chǎn)生的那個(gè) Promise 對(duì)象上了。
showDialog()
?不能聲明為?async
?的,否則返回出來的 Promise 對(duì)象也不是里面產(chǎn)生的那一個(gè)。
別忘了?await
。
以上就是vue.js前端網(wǎng)頁彈框異步行為示例分析的詳細(xì)內(nèi)容,更多關(guān)于vue.js前端異步網(wǎng)頁彈框的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Pure admin-Router標(biāo)簽頁配置與頁面持久化實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Pure admin-Router標(biāo)簽頁配置與頁面持久化實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01element多個(gè)table實(shí)現(xiàn)同步滾動(dòng)的示例代碼
本文主要介紹了element多個(gè)table實(shí)現(xiàn)同步滾動(dòng),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09vue-admin-template框架搭建及應(yīng)用小結(jié)
?vue-admin-template是基于vue-element-admin的一套后臺(tái)管理系統(tǒng)基礎(chǔ)模板(最少精簡(jiǎn)版),可作為模板進(jìn)行二次開發(fā),這篇文章主要介紹了vue-admin-template框架搭建及應(yīng)用,需要的朋友可以參考下2023-05-05記一次Vue中$route序列號(hào)報(bào)錯(cuò)
本文主要介紹了記一次Vue中$route序列號(hào)報(bào)錯(cuò),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04vue動(dòng)態(tài)綁定v-model屬性名方式
這篇文章主要介紹了vue動(dòng)態(tài)綁定v-model屬性名方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08Vue3+Vite中不支持require的方式引入本地圖片的解決方案
這篇文章主要介紹了Vue3+Vite中不支持require的方式引入本地圖片的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01