在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1. 序网页弹框是个很常见的功能,比如需要告知用户消息的时候 (Alert),需要用户进行确认的时候 (Confirm),需要用户补充一点信息的时候 (Prompt) …… 甚至可以弹框让用户填写表单 (Modal Dialog)。 弹框之后,开发者需要知道这个弹框是什么时候关闭以便进行接下来的操作。 在比较古老的 UI 组件中,这个事情是通过事件回调来进行的,大概长这样: showDialog(content, title, { closed: function() { console.log("对话框已关闭"); } }) 不过对话框的行为。你看,它弹出来了,但它不会阻塞后面的代码,而且开发者并不知道什么时候关闭,因为这是用户行为。既然是异步,封装成 Promise 使用 async function asyncShowDialog(content, title, options) { return new Promise(resolve => { showDialog(content, title, { ...options, closed: resolve }); }); } (async () => { await asyncShowDialog(content, title); console.log("对话框已关闭"); })(); 弹框的基本的异步行为就是这么简单,就这么结束?心有不甘,再研究研究! 2. 找两个弹框组件看看Ant Design Vue 使用了事件的形式,点击“确定”按钮会触发 这两个事件处理函数通过参数对象的 Modal.confirm({ ... onOk() { // 点击「确定」按钮后,会显示加载动画,并在一秒后关闭对话框 return new Promise(resolve => { setTimeout(resolve, 1000); }); } ... }); 而 Element Plus 使用了 Promise 形式,打开对话框时,并不是把确定或取消的处理函数以参数的形式传入,而是直接返回一个 Promise 对象,供开发者通过 try { await ElMessageBox.confirm(...); // 按下确定按钮在这里处理 } catch(err) { // 按下取消按钮在这里处理 } Element Plus 的这种处理方式,要在对话框关闭之后才能处理业务。这也是使用 Promise 的局限 —— 对于一个已经封装好的 Promise 对象,很难在其中插入新的逻辑。 如果使用
所以类似 Ant Design 的处理可以这样写: try { await ElMessageBox.confirm({ ... beforeClose: async (action, instance, done) => { await new Promise(resolve => setTimeout(resolve, 1000)); done(); } }); // 按下确定按钮在这里处理 } catch(err) { // 按下取消按钮在这里处理 } 3. 自己肝一个分析了两个弹框组件的行为处理,我们已经知道,一个体验良好的弹框组件应该具备如下特征:
接下来,我们自己写一个,看看是如何实现上述特征的。不过,既然我们主要研究的是行为而不是数据处理,所以不用 Vue 框架,直接用 DOM 操作,然后引入 jQuery 来简化 DOM 处理。 对话框的 HTML 骨架也比较简单:下面一层蒙板,上面一个固定大小的 <div class="dialog" id="dialogTemplate"> <div class="dialog-window"> <div class="dialog-title">对话框标题</div> <div class="dialog-content">对话框的内容</div> <div class="dialog-operation"> <button type="button" class="ensure-button">确定</button> <button type="button" class="cancel-button">取消</button> </div> </div> </div> 这里把它定义成一个模板,希望每次都从它克隆一个 DOM 出来呈现,关闭即毁。 样式表的内容较长,可以从后面的示例链接去获取。代码及代码的进化过程才是本文的重点。 最简单的呈现是利用 jQuery 克隆一个显示出来,但显示前一定要记得删除掉 $("#dialogTemplate").clone().removeAttr("id").appendTo("body").show(); 把它封装成一个函数,并且添加对「确定」和「取消」按钮的处理: function showDialog(content, title) { const $dialog = $("#dialogTemplate").clone().removeAttr("id"); // 设置对话框的标题和内容(简单示例,所以只处理文本) $dialog.find(".dialog-title").text(title); $dialog.find(".dialog-content").text(content); // 通过事件代理(也可以不用代理)处理两个按钮事件 $dialog .on("click", ".ensure-button", () => { $dialog.remove(); }) .on("click", ".cancel-button", () => { $dialog.remove(); }); $dialog.appendTo("body").show(); } 弹框的基本逻辑就出来了。现在做两点优化:① 把 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到这一步,弹框已经可以正常弹出/关闭了,但是没办法注入「确定」或「取消」的逻辑代码。前面提到可以通过事件或 Promise 两种形式来提供接口,这里使用 Promise 的方式。如果点「确定」就 resolve,点「取消」就 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(); } 封装好了,但有个问题: 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. 确定时允许异步等待不管「确定」还是「取消」都可以保持弹框显示,进行异步等待。但作为示例,这里只处理「确定」的情况。 这个异步等待过程要注入到从弹窗中,只能采用参数注入的形式。所以需要为 先修改 function showDialog(conent, title, options = {}) { ... } 然后再处理 $dialog.on("click", ".ensure-button", ...) 事件: $dialog .on("click", ".ensure-button", async () => { const { onOk } = options; // 从 options 中拿到 onOk,如果它是一个函数才需要等待处理 if (typeof onOk === "function") { const r = onOk(); // 判断 onOk() 的结果是不是一个 Promise Like 对象 // 只有 Promise Like 对象才需要异步等待 if (typeof r?.then === "function") { const $button = $dialog.find(".ensure-button"); // 异步等待过程中需要给用户一定反馈 // 这里偷懒没有使用加载动画,只用文字来进行反馈 $button.text("处理中..."); await r; // 因为在完成之后,关闭之前有 200 毫秒的渐隐过程, // 所以把按钮文本改为“完成”,给用户及时反馈是有必要的 $button.text("完成"); } } await destroy(); resolve("ok"); }) 现在这个弹框的行为基本上处理完了,调用的示例: const result = await showDialog( "你好,这里是对话框的内容", "打个招呼", { onOk: () => new Promise((resolve) => { setTimeout(resolve, 3000); }) } ).catch(msg => msg); // 这里把取消引起的 reject 变成 resolve,避免使用 try...catch... console.log(result === "ok" ? "按下确定" : "按下取消"); 3.3. 细节完善都有对话框了最后还用 但是现在的 async function showDialog(content, title, options = {}) { ... if (options.type === "alert") { $dialog.find(".cancel-button").remove(); } ... } 然后,最后的 showDialog(result === "ok" ? "按下确定" : "按下取消", "提示", { type: "alert" }); 3.4. 改革如果不喜欢在 const dialog = showDialog("你好,这里是对话框的内容", "打个招呼"); // 把处理函数注入到 promise 的 onOk dialog.onOk = () => new Promise((resolve) => { setTimeout(resolve, 3000); }); const result = await dialog.catch(msg => msg); showDialog(result === "ok" ? "按下确定" : "按下取消", "提示", { type: "alert" }); 这里有几点要注意:
别忘了 以上就是vue.js前端网页弹框异步行为示例分析的详细内容,更多关于vue.js前端异步网页弹框的资料请关注极客世界其它相关文章! |
请发表评论