vue.js前端网页弹框异步行为示例分析
目录
- 1. 序
- 2. 找两个弹框组件看看
- 3. 自己肝一个
- 3.1. 封装 Promise
- 3.2. 确定时允许异步等待
- 3.3. 细节完善
- 3.4. 改革
1. 序 网页弹框是个很常见的功能,比如需要告知用户消息的时候 (Alert),需要用户进行确认的时候 (Confirm),需要用户补充一点信息的时候 (Prompt) …… 甚至可以弹框让用户填写表单 (Modal Dialog)。
弹框之后,开发者需要知道这个弹框是什么时候关闭以便进行接下来的操作。
在比较古老的 UI 组件中,这个事情是通过事件回调来进行的,大概长这样:
showDialog(content, title, {closed: function() { console.log("对话框已关闭"); }})
不过对话框的行为。你看,它弹出来了,但它不会阻塞后面的代码,而且开发者并不知道什么时候关闭,因为这是用户行为。既然是异步,封装成 Promise 使用
await
语法来调用会更舒服一些。简单的封装大概可以这样: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 使用了事件的形式,点击“确定”按钮会触发
ok
事件,点击“取消”或者右上角的关闭按钮会触发 cancel
事件。这两个事件处理函数通过参数对象的
onOk
和 onCancel
属性挂载进去。看起来平淡无奇,但如果处理事件返回的是一个 Promise 对象,点击按钮之后会出现加载动画并等待直到 Promise 对象完成之后才会关闭对话框。这种设计把异步等待动画组合到弹框当中,简洁直观,代码写起来也很方便。以 confirm 对话框为例:Modal.confirm({...onOk() {// 点击「确定」按钮后,会显示加载动画,并在一秒后关闭对话框return new Promise(resolve => {setTimeout(resolve, 1000); }); }...});
而 Element Plus 使用了 Promise 形式,打开对话框时,并不是把确定或取消的处理函数以参数的形式传入,而是直接返回一个 Promise 对象,供开发者通过
.then()/.catch()
或者 await
处理。示例:try {await ElMessageBox.confirm(...); // 按下确定按钮在这里处理} catch(err) {// 按下取消按钮在这里处理}
Element Plus 的这种处理方式,要在对话框关闭之后才能处理业务。这也是使用 Promise 的局限 —— 对于一个已经封装好的 Promise 对象,很难在其中插入新的逻辑。
如果使用
ElMessageBox
的时候也想像 Ant Design 那样在关闭前进行一些异步操作,只能去找找看它是否提供了关闭前的处理事件。一找还真找到了,它有 beforeClose
事件。该事件的处理函数签名是 beforeClose(action, instance, done)
:action
表示按了哪个按钮,取值可能是 "confirm"
、"cancel"
和 "close"
(不用解释了吧)。instance
是 MessageBox 实例,可以使用它来控制一些界面效果,比如instance.confirmButtonLoading = true
会在“确定”按钮上显示加载动画,instance.confirmButtonText
可以用来改变按钮文本 …… 这些操作在进行异步等待时可以提供更好的用户体验。done
是一个函数,调用它表示 beforeClose()
的异步处理完成,对话框现在可以关闭了!所以类似 Ant Design 的处理可以这样写:
try {await ElMessageBox.confirm({...beforeClose: async (action, instance, done) => {await new Promise(resolve => setTimeout(resolve, 1000)); done(); }}); // 按下确定按钮在这里处理} catch(err) {// 按下取消按钮在这里处理}
3. 自己肝一个 分析了两个弹框组件的行为处理,我们已经知道,一个体验良好的弹框组件应该具备如下特征:
- 提供基于 Promise 的异步控制能力(Ant Design Vue 虽然没有提供,但是像“序”中那样封装一下就可以)。
- 允许在关闭前进行一些操作,甚至是异步操作。
- 提供异步加载过程中的界面反馈,而且最好不需要开发者来控制(从这点来说 Ant Design 比 Element Plus 方便)。
对话框的 HTML 骨架也比较简单:下面一层蒙板,上面一个固定大小的
层,内部再用
划分成标题、内容、操作区三块:对话框标题对话框的内容
这里把它定义成一个模板,希望每次都从它克隆一个 DOM 出来呈现,关闭即毁。
样式表的内容较长,可以从后面的示例链接去获取。代码及代码的进化过程才是本文的重点。
最简单的呈现是利用 jQuery 克隆一个显示出来,但显示前一定要记得删除掉
id
属性,并把它添加到
中去:$("#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(); }
弹框的基本逻辑就出来了。现在做两点优化:① 把
$dialog.remove()
封装成函数,便于对关闭对话框进行统一处理(代码复用) ② 使用 .show()
呈现太过生硬,改为 fadeIn(200)
;同理,应该在 .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
到这一步,弹框已经可以正常弹出/关闭了,但是没办法注入「确定」或「取消」的逻辑代码。前面提到可以通过事件或 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(); }
封装好了,但有个问题:
destroy()
是个异步过程,但代码并没有等它结束,所以 showDialog()
完成异步处理之后还在进行 fadeOut()
操作和 remove()
操作。要解决这个问题,只能封装 destory()
。当然调用的时候也别忘了加 await
,而加 await
就要把外层函数声明为 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. 确定时允许异步等待
不管「确定」还是「取消」都可以保持弹框显示,进行异步等待。但作为示例,这里只处理「确定」的情况。
这个异步等待过程要注入到从弹窗中,只能采用参数注入的形式。所以需要为
showDialog()
添加一个 options
参数,允许注入一个处理函数给 onOk
属性,如果这个处理函数返回 Promise Like,就进行异步等待。先修改
showDialog()
接口:function showDialog(conent, title, options = {}) { ... }
【vue.js前端网页弹框异步行为示例分析】然后再处理 $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. 细节完善
都有对话框了最后还用
console.log(...)
实在有点不妥,直接弹框提示消息不更好?但是现在的
showDialog()
只处理了 Confirm 弹框,没有处理 Alert 弹框 …… 问题不大,在 options
里加个 type
好了。如果 type
是 "alert"
就把「取消」按钮干掉。async function showDialog(content, title, options = {}) {...if (options.type === "alert") {$dialog.find(".cancel-button").remove(); }...}
然后,最后的
console.log(...)
可以进化一下:showDialog(result === "ok" ? "按下确定" : "按下取消", "提示", { type: "alert" });
3.4. 改革
如果不喜欢在
options
中注入处理函数,还可以换个法子,在返回的 Promise 对象中注入。先在 .ensure-button
的事件中把 const { onOk } = options
改为 const { onOk } = promise
,也就是从 promise
中获取注入的 onOk
。然后改调用部分:const dialog = showDialog("你好,这里是对话框的内容", "打个招呼"); // 把处理函数注入到 promise 的 onOkdialog.onOk = () => new Promise((resolve) => { setTimeout(resolve, 3000); }); const result = await dialog.catch(msg => msg); showDialog(result === "ok" ? "按下确定" : "按下取消", "提示", { type: "alert" });
这里有几点要注意:
dialog
必须只能是 showDialog()
直接返回的。如果调用了 .catch()
将会得到另一个 Promise 对象,此时再注入 onOk
就注入不到 showDialog()
里面产生的那个 Promise 对象上了。showDialog()
不能声明为 async
的,否则返回出来的 Promise 对象也不是里面产生的那一个。别忘了
await
。以上就是vue.js前端网页弹框异步行为示例分析的详细内容,更多关于vue.js前端异步网页弹框的资料请关注脚本之家其它相关文章!
推荐阅读
- 使用协程爬取网页,计算网页数据大小
- Jsr303做前端数据校验
- web网页模板|如此优秀的JS轮播图,写完老师都沉默了
- 7、前端--jQuery简介、基本选择器、基本筛选器、属性选择器、表单选择器、筛选器方法、节点操作、绑定事件
- 前端代码|前端代码 返回顶部 backToTop
- VueX(Vuex|VueX(Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式)
- vue.js|vue中使用axios封装成request使用
- 前端|web前端dya07--ES6高级语法的转化&render&vue与webpack&export
- 前端自学笔记01
- js保留自定义小数点