全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2023-09-11_极致舒适的Vue弹窗使用方案丨文末福利

您的位置:首页 >> 新闻 >> 行业资讯

极致舒适的Vue弹窗使用方案丨文末福利 点击关注公众号,回复”福利”即可赢取掘金周边 一个Hook让你体验极致舒适的Dialog使用方式! Dialog地狱为啥是地狱? 因为凡是有Dialog出现的页面,其代码绝对优雅不起来!因为一旦你在也个组件中引入Dialog,就最少需要额外维护一个visible变量。如果只是额外维护一个变量这也不是不能接受,可是当同样的Dialog组件,即需要在父组件控制它的展示与隐藏,又需要在子组件中控制。 为了演示我们先实现一个MyDialog组件,代码来自ElementPlus的Dialog示例 scriptsetuplang="ts" import{computed}from'vue'; import{ElDialog}from'element-plus'; constprops=defineProps{ visible:boolean; title?:string; } constemits=defineEmits{ (event:'update:visible',visible:boolean):void; (event:'close'):void; } constdialogVisible=computedboolean({ get(){ returnprops.visible; }, set(visible){ emits('update:visible',visible); if(!visible){ emits('close'); } }, }); /script template ElDialogv-model="dialogVisible":title="title"width="30%" spanThisisamessage/span template#footer span el-button@click="dialogVisible=false"Cancel/el-button el-buttontype="primary"@click="dialogVisible=false"Confirm/el-button /span /template /ElDialog /template 演示场景就像下面这样: 示例代码如下: scriptsetuplang="ts" import{ref}from'vue'; import{ElButton}from'element-plus'; importCompfrom'./components/Comp.vue'; importMyDialogfrom'./components/MyDialog.vue'; constdialogVisible=refboolean(false); constdialogTitle=refstring(''); consthandleOpenDialog=()={ dialogVisible.value=true; dialogTitle.value='父组件弹窗'; }; consthandleComp1Dialog=()={ dialogVisible.value=true; dialogTitle.value='子组件1弹窗'; }; consthandleComp2Dialog=()={ dialogVisible.value=true; dialogTitle.value='子组件2弹窗'; }; /script template div ElButton@click="handleOpenDialog"打开弹窗/ElButton Comptext="子组件1"@submit="handleComp1Dialog"/Comp Comptext="子组件2"@submit="handleComp2Dialog"/Comp MyDialogv-model:visible="dialogVisible":title="dialogTitle"/MyDialog /div /template 这里的MyDialog会被父组件和两个Comp组件都会触发,如果父组件并不关心子组件的onSubmit事件,那么这里的submit在父组件里唯一的作用就是处理Dialog的展示!!!??这样真的好吗?不好! 来分析一下,到底哪里不好! 「MyDialog本来是submit动作的后续动作,所以理论上应该将MyDialog写在Comp组件中。但是这里为了管理方便,将MyDialog挂在父组件上,子组件通过事件来控制MyDialog。」 「再者,这里的handleComp1Dialog和handleComp2Dialog函数除了处理MyDialog外,对于父组件完全没有意义却写在父组件里。」 如果这里的Dialog多的情况下,简直就是Dialog地狱啊!?? 理想的父组件代码应该是这样: scriptsetuplang="ts" import{ElButton}from'element-plus'; importCompfrom'./components/Comp.vue'; importMyDialogfrom'./components/MyDialog.vue'; consthandleOpenDialog=()={ //处理MyDialog }; /script template div ElButton@click="handleOpenDialog"打开弹窗/ElButton Comptext="子组件1"/Comp Comptext="子组件2"/Comp /div /template 在函数中处理弹窗的相关逻辑才更合理。 解决之道??朕观之,是书之文或不雅,致使人之心有所厌,何得无妙方可解决? 依史记之辞曰:“天下苦Dialog久矣,苦楚深深,望有解脱之道。”于是,诸位贤哲纷纷举起讨伐Dialog之旌旗,终“命令式Dialog”逐渐突破困境之境地。 没错现在网上对于Dialog的困境,给出的解决方案基本上就“命令式Dialog”看起来比较优雅!这里给出几个网上现有的命令式Dialog实现。 命令式一 吐槽一下~,这种是能在函数中处理弹窗逻辑,但是缺点是MyDialog组件与showMyDialog是两个文件,增加了维护的成本。 命令式二基于第一种实现的问题,不就是想让MyDialog.vue和.js文件合体吗?于是诸位贤者想到了JSX。于是进一步的实现是这样: 嗯,这下完美了!?? 完美?还是要吐槽一下~ 如果我的系统中有很多弹窗,难道要给每个弹窗都写成这样吗?这种兼容JSX的方式,需要引入支持JSX的依赖!如果工程中不想即用template又用JSX呢?如果已经存在使用template的弹窗了,难道推翻重写吗?...思考首先承认一点命令式的封装的确可以解决问题,但是现在的封装都存一定的槽点。 如果有一种方式,「即保持原来对话框的编写方式不变,又不需要关心JSX和template的问题,还保存了命令式封装的特点」。这样是不是就完美了? 那真的可以同时做到这些吗? 如果存在一个这样的Hook可以将状态驱动的Dialog,转换为命令式的Dialog吗,那不就行了? 它来了:useCommandComponent 父组件这样写: scriptsetuplang="ts" import{ElButton}from'element-plus'; import{useCommandComponent}from'../../hooks/useCommandComponent'; importCompfrom'./components/Comp.vue'; importMyDialogfrom'./components/MyDialog.vue'; constmyDialog=useCommandComponent(MyDialog); /script template div ElButton@click="myDialog({title:'父组件弹窗'})"打开弹窗/ElButton Comptext="子组件1"/Comp Comptext="子组件2"/Comp /div /template Comp组件这样写: scriptsetuplang="ts" import{ElButton}from'element-plus'; import{useCommandComponent}from'../../../hooks/useCommandComponent'; importMyDialogfrom'./MyDialog.vue'; constmyDialog=useCommandComponent(MyDialog); constprops=defineProps{ text:string; } /script template div span{{props.text}}/span ElButton@click="myDialog({title:props.text})"提交(需确认)/ElButton /div /template 对于MyDialog无需任何改变,保持原来的样子就可以了! useCommandComponent真的做到了,「即保持原来组件的编写方式,又可以实现命令式调用」! 使用效果: 是不是感受到了莫名的舒适??? 不过别急??,要想体验这种极致的舒适,你的Dialog还需要遵循两个约定! 两个约定如果想要极致舒适的使用useCommandComponent,那么弹窗组件的编写就需要遵循一些约定(「其实这些约定应该是弹窗组件的最佳实践」)。 约定如下: 「弹窗组件的props需要有一个名为visible的属性」,用于驱动弹窗的打开和关闭。「弹窗组件需要emit一个close事件」,用于弹窗关闭时处理命令式弹窗。如果你的弹窗组件满足上面两个约定,那么就可以通过useCommandComponent极致舒适的使用了!! ?这两项约定虽然不是强制的,但是这确实是最佳实践!不信你去翻所有的UI框看看他们的实现。我一直认为学习和生产中多学习优秀框架的实现思路很重要! ?如果不遵循约定这时候有的同学可能会说:「哎嘿,我就不遵循这两项约定呢?我的弹窗就是要标新立异的不用visible属性来控制打开和关闭,我起名为dialogVisible呢?我的弹窗就是没有close事件呢?我的事件是具有业务意义的submit、cancel呢?」... 得得得,如果真的没有遵循上面的两个约定,依然可以舒适的使用useCommandComponent,只不过在我看来没那么极致舒适!虽然不是极致舒适,但也要比其他方案舒适的多! 如果你的弹窗真的没有遵循“「两个约定」”,那么你可以试试这样做: scriptsetuplang="ts" //... constmyDialog=useCommandComponent(MyDialog); consthandleDialog=()={ myDialog({ title:'父组件弹窗', dialogVisible:true, onSubmit:()=myDialog.close(), onCancel:()=myDialog.close(), }; /script template div ElButton@click="handleDialog"打开弹窗/ElButton !--...-- /div /template 如上,只需要在调用myDialog函数时在props中将驱动弹窗的状态设置为true,在需要关闭弹窗的事件中调用myDialog.close()即可! 这样是不是看着虽然没有上面的极致舒适,但是也还是挺舒适的? 源码与实现实现思路对于useCommandComponent的实现思路,依然是「命令式封装」。相比于上面的那两个实现方式,useCommandComponent是将组件作为参数传入,这样「保持组件的编写习惯不变」。并且useCommandComponent「遵循单一职责原则」,只做好组件的挂载和卸载工作,提供「足够的兼容性」。 ?其实useCommandComponent有点像React中的高阶组件的概念 ?源码Github源码地址 源码不长,也很好理解!在实现useCommandComponent的时候参考了ElementPlus的MessageBox。 源码如下: import{AppContext,Component,ComponentPublicInstance,createVNode,getCurrentInstance,render,VNode}from'vue'; exportinterfaceOptions{ visible?:boolean; onClose?:()=void; appendTo?:HTMLElement|string; [key:string]:unknown; } exportinterfaceCommandComponent{ (options:Options):VNode; close:()=void; } constgetAppendToElement=(props:Options):HTMLElement={ letappendTo:HTMLElement|null=document.body; if(props.appendTo){ if(typeofprops.appendTo==='string'){ appendTo=document.querySelectorHTMLElement(props.appendTo); } if(props.appendToinstanceofHTMLElement){ appendTo=props.appendTo; } if(!(appendToinstanceofHTMLElement)){ appendTo=document.body; } } returnappendTo; }; constinitInstance=TextendsComponent( Component:T, props:Options, container:HTMLElement, appContext:AppContext|null=null )={ constvNode=createVNode(Component,props); vNode.appContext=appContext; render(vNode,container); getAppendToElement(props).appendChild(container); returnvNode; }; exportconstuseCommandComponent=TextendsComponent(Component:T):CommandComponent={ constappContext=getCurrentInstance()?.appContext; //补丁:Component中获取当前组件树的provides if(appContext){ constcurrentProvides=(getCurrentInstance()asany)?.provides; Reflect.set(appContext,'provides',{...appContext.provides,...currentProvides}); } constcontainer=document.createElement('div'); constclose=()={ render(null,container); container.parentNode?.removeChild(container); constCommandComponent=(options:Options):VNode={ if(!Reflect.has(options,'visible')){ options.visible=true; } if(typeofoptions.onClose!=='function'){ options.onClose=close; }else{ constoriginOnClose=options.onClose; options.onClose=()={ originOnClose(); close(); } constvNode=initInstance(Component,options,container,appContext); constvm=vNode.component?.proxyasComponentPublicInstanceOptions for(constpropinoptions){ if(Reflect.has(options,prop)!Reflect.has(vm.$props,prop)){ vm[propaskeyofComponentPublicInstance]=options[prop]; } } returnvNode; CommandComponent.close=close; returnCommandComponent; }; exportdefaultuseCommandComponent; 除了命令式的封装外,我加入了const appContext = getCurrentInstance()?.appContext;。这样做的目的是,传入的组件在这里其实已经独立于应用的Vue上下文了。为了让组件依然保持和调用方相同的Vue上下文,我这里加入了获取上下文的操作! 基于这个情况,在使用useCommandComponent时需要保证它在setup中被调用,而不是在某个点击事件的处理函数中哦~ 源码补丁非常感谢@bluryar关于命令式组件无法获取当前组件树的 injection 的指出!!???? 趁着热乎,我想到一个解决获取当前injection的解决办法。那就是将当前组件树的provides与appContext.provides合并,这样传入的弹窗组件就可以顺利的获取到app和当前组件树的provides了! 最后如果你觉得useCommandComponent对你在开发中有所帮助,麻烦多点赞评论收藏?? 点击小卡片,参与粉丝专属福利!! 如果文章对你有帮助的话欢迎「关注+点赞+收藏」? ? 阅读原文

上一篇:2023-03-22_智云校园大使第二期招募,福利升级速来投递! 下一篇:2024-12-19_2024年十大创新品牌(投票)

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价