网页弹出框的异步行为分析
时间:2023-03-28 13:57:10
HTML
1.排序网页弹出框是一个很常见的功能,比如当有消息需要通知用户时(Alert),当用户需要确认时(Confirm),当用户需要添加一些信息时(Prompt)。..你甚至可以弹出一个框让用户填写表单(ModalDialog)。弹窗之后,开发者需要知道弹窗什么时候关闭,才能进行下一步的操作。在旧的UI组件中,这件事是通过事件回调来完成的,它看起来像这样:对话框的行为。你看,弹出来了,但是并没有把后面的代码屏蔽掉,开发者也不知道什么时候关闭,因为这是用户行为。既然是异步的,那么封装成Promise,用await语法调用会更舒服。一个简单的封装可能看起来像这样:asyncfunctionasyncShowDialog(content,title,options){returnnewPromise(resolve=>{showDialog(content,title,{...options,closed:resolve});});}(async()=>{awaitasyncShowDialog(content,title);console.log("对话框关闭");})();弹出框的基本异步行为就这么简单,就这样结束了?不愿意就再研究再研究!2.寻找两个弹框组件。既然是研究,就先找到现有的轮子。这里随便选两个,都是基于Vue3框架的ElementPlus的MessageBoxAntDesingVue的Modal。AntDesignVue使用事件的形式。单击“确定”按钮将触发ok事件。点击右上角的“取消”或关闭按钮触发取消事件。这两个事件处理程序是通过参数对象的onOk和onCancel属性挂载的。这看起来微不足道,但如果事件处理程序返回一个Promise,按钮将被点击,加载动画将出现,并等待Promise完成后再关闭对话框。这种设计将异步等待动画结合到弹框里,简单直观,代码写起来也很方便。以确认对话框为例:Modal.confirm({...onOk(){//点击“确定”按钮后,会显示加载动画,一秒后关闭对话框returnnewPromise(resolve=>{setTimeout(resolve,1000);});}...});ElementPlus使用Promise形式。在打开对话框时,没有将确认或取消处理函数作为参数传入,而是直接返回一个Promise对象,供开发者通过.then()/.catch()或await来处理。示例:try{awaitElMessageBox.confirm(...);//按下OK键这里处理}catch(err){//按下取消键这里处理}ElementPlus的这种处理方式需要在dialog业务中处理,只有在box关闭后才能处理。这也是使用Promise的局限性——对于一个已经封装好的Promise对象,很难往里面插入新的逻辑。如果在使用ElMessageBox的时候想像AntDesign一样在关闭前进行一些异步操作,那么只能看它是否提供了关闭前的处理事件。经过搜索,我找到了。它有一个beforeClose事件。这个事件的处理函数签名是beforeClose(action,instance,done):action表示按下了哪个按钮,取值可能是“confirm”、“cancel”和“close”(不用解释)。instance是MessageBox的一个实例,你可以用它来控制一些界面效果,比如instance.confirmButtonLoading=true会在“确定”按钮上显示loading动画,instance.confirmButtonText可以用来改变按钮文字...这些操作可以在异步等待的同时进行,提供更好的用户体验。done是一个被调用的函数,表示beforeClose()的异步处理已完成,现在可以关闭对话框了!所以类似于AntDesign的处理可以这样写:);完毕();}});//按下确定按钮,这里处理}catch(err){//按下取消按钮,这里处理}3.我们已经分析了两个弹出组件的行为处理,我们已经知道了,一个弹一个好的体验的盒子组件应该具备以下特点:提供基于Promise的异步控制能力(虽然AntDesignVue没有提供,但是可以像“前言”一样进行封装)。允许在关闭之前执行一些操作,甚至是异步操作。在异步加载时提供界面反馈,最好不需要开发者去控制(从这点上来说,AntDesign比ElementPlus更方便)。去CodePen看demo代码下面我们自己写一个看看上面的功能是如何实现的。但是由于我们主要研究的是行为而不是数据处理,所以我们不使用Vue框架,直接使用DOM操作,然后引入jQuery来简化DOM处理。对话框的HTML骨架也比较简单:下面一个遮罩层,上面一个固定大小的
确定取消 这里定义为模板,希望每次从中克隆一个DOM并呈现,关闭时销毁。样式表的内容比较长,可以从下面的示例链接中获取。代码和代码的演变是本文的重点。最简单的渲染是使用jQuery克隆一个显示,但是在显示之前,一定要删除id属性并添加到中:$("#dialogTemplate").clone().removeAttr("id").appendTo("body").show();将其封装成一个函数,并添加“确定”和“取消”按钮的处理:functionshowDialog(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)。函数showDialog(...){...constdestroy=()=>{$dialog.fadeOut(200,()=>$dialog.remove());};$dialog.on("click",".ensure-button",destroy).on("click",".cancel-button",destroy);$dialog.appendTo("body").fadeIn(200);}3.1。封装Promise到这一步,弹出框已经可以正常弹出/关闭,但是没办法注入“OK”或者“Cancel”的逻辑代码。前面说了接口可以提供事件或者Promise两种形式,这里就用到了Promise。点击“确定”则解决,点击“取消”则拒绝。functionshowDialog(...){...constpromise=newPromise((resolve,reject)=>{$dialog.on("click",".ensure-button",()=>{destroy();resolve("ok");}).on("click",".cancel-button",()=>{destroy();reject("cancel");});});$dialog.appendTo("body").fadeIn(200);returnpromise();}被打包了,但是有一个问题:destroy()是一个异步过程,但是代码并没有等到它结束,所以showDialog()在异步处理之后仍然要进行fadeOut()操作和remove()操作。要解决这个问题,只能封装destroy()。当然调用的时候不要忘记加上await,要加上await,必须把外层函数声明为async:functionshowDialog(...){...constdestroy=()=>{returnnewPromise(resolve=>{$dialog.fadeOut(200,()=>{$dialog.remove();resolve();});});};constpromise=newPromise((resolve,reject)=>{$dialog.on("click",".ensure-button",async()=>{awaitdestroy();resolve("ok");}).on("click",".cancel-button",async()=>{awaitdestroy();reject("cancel");});});...}3.2。确认时允许异步等待,无论“确定”还是“取消”,弹出框都可以一直显示,进行异步等待。但是作为一个例子,这里只处理“OK”的情况。这个异步等待过程只能以参数注入的形式注入到slave弹窗中。所以你需要在showDialog()中添加一个options参数,它允许你在onOk属性中注入一个处理函数。如果处理函数返回PromiseLike,就会异步等待。先修改showDialog()接口:functionshowDialog(conent,title,options={}){...}然后处理$dialog.on("click",".ensure-button",...)事件:$dialog.on("click",".ensure-button",async()=>{const{onOk}=options;//从options获取onOk,如果是函数,需要等待处理if(typeofonOk==="function"){constr=onOk();//判断onOk()的结果是否为PromiseLike对象//只有PromiseLike对象需要异步等待if(typeofr?.then==="function"){const$button=$dialog.find(".ensure-button");//需要在异步等待过程中给用户一些反馈//这里偷懒没有使用加载动画,onlytextforfeedback$button.text("Processing...");awaitr;//因为完成后关闭前有一个200毫秒的淡入淡出过程,//所以需要将按钮文字改为“Complete”给用户及时反馈$button.text("Complete");}}await破坏();解决(“确定”);})现在这个弹框的行为基本说完了,调用的例子:constresult=awaitshowDialog("YouOk,hereisthecontentofthedialog","Sayhello",{onOk:()=>newPromise((resolve)=>{setTimeout(resolve,3000);})}).catch(msg=>msg);//这里将取消导致的拒绝改为解决,避免使用try...catch...console.log(result==="ok"?"PressOK":"PressCancel");3.3.有对话框看细节,最后用console.log(。。。)有点不合适。直接弹出一个框来提示消息不是更好吗?但是现在的showDialog()只处理了Confirm弹框,没有处理Alert弹框……问题不大,给options加个type就行了。如果类型是“alert”,就杀掉“Cancel”按钮。异步函数showDialog(content,title,options={}){...if(options.type==="alert"){$dialog.find(".cancel-button").remove();}...}然后,最后一个console.log(...)可以进化:showDialog(result==="ok"?"PressOK":"PressCancel","Prompt",{type:"alert"});3.4.改革如果你不喜欢在选项中注入处理函数,你可以使用另一种方式将它们注入到返回的Promise对象中。先将.ensure-button事件中的const{onOk}=options改为const{onOk}=promise,即从promise中获取注入的onOk。然后更改调用部分:constdialog=showDialog("Hello,hereisthecontentofthedialog","Sayhello");//将处理函数注入promise的onOkdialog.onOk=()=>newPromise((resolve)=>{setTimeout(resolve,3000);});constresult=awaitdialog.catch(msg=>msg);showDialog(result==="ok"?"pressOK":"presscancel","提示",{type:"alert"});这里有几点需要注意:dialog只能由showDialog()直接返回。如果你调用.catch(),你会得到另一个Promise对象。如果此时注入onOk,将无法注入showDialog()中生成的Promise对象。showDialog()不能声明为async,否则返回的Promise对象不是里面生成的。别忘了等待。