当前位置: 首页 > Web前端 > HTML5

前端思考(一)

时间:2023-04-05 20:02:47 HTML5

不知不觉工作了很久。对于发展,恰好处于十字路口:是走技术路线还是管理路线,这确实是一个非常艰难的选择。毕竟,这影响到我身后的路是否越走越远?对于我自己来说,还是想继续一路走技术,因为回头看自己为什么从事开发,做一个早出晚归的程序员,更多的是对技术的向往。而我觉得这段时间只关注如何做好一件事,却很少关注如何做好一件事;做好和做好虽然一字之差,但所付出的专注和努力是后者的好几倍,更考验自己的技术、大局观、沟通能力等;所以现在给自己定一个目标,想想前5年怎么做,然后再想想接下来5年怎么做好每一件事。温馨提示以下内容会夹杂着自己的各种想法,或者遇到的小问题,如有雷同的想法和场景,纯属巧合。如何把后台管理项目做的更好对于后台管理项目,我感觉很多前端(包括我)都觉得没有必要在上面浪费任何时间:优化,布局,体验。公司只有一些业务和产品;更多的时间可以花在真正面向用户的业务或学习新技术上。好吧,直到有一天,我们的测试同学认真负责地测试了这些页面,报了一堆bug,我陷入了沉思:这之后怎么这么难做!怨就是怨,后面的管理项目也需要好好想想。毕竟,您不想一直被这些后台管理错误所困扰。以下是我的个人总结。后台管理项目要快速稳定,最好建立一个通用的正则表达式库,比如:电话号码、手机号码、姓名、地址等;最好和后端同学协商,减少前端可以通过,但是后端校验失败的情况。确定表单的交互形式:预检(按钮不要灰显),后检(点击提交做检查提示);这两个check各有优缺点:pre-check可以减少挫败感,但是Form项输入多或复杂容易造成混淆(按钮总是灰色的,不知道哪些地方没填,所以我需要来回检查);post-checking反倒是适得其反,容易增加挫败感,可能要提交三四次。只有这样,才能正确提交整个表单,但在复杂的情况下,也可以明确提示哪些表单项没有丢失或格式错误。为什么要花时间在这种地方纠结呢,因为在处理表单的时候,这两个交互的选择会影响代码的写法,而且很明显post-check比pre-check更容易实现(pre-check需要监控整个表单模型对象的每个字段值的变化,每次变化都要验证表单);事后检查可以更方便,甚至可以靠后端同学返回的错误信息直接提示用户。毕竟后端同学做了比较完整的验证。个人认为对于后期管理的项目,应该更倾向于后期检查。毕竟,后台管理页面的表格往往很复杂(但测试有点喜欢预检)。返回的json结构一定要确定好,一般类似这样:{code:'0000',message:'',body:{}}但是有时候他们后台的数据往往来自其他系统,有的时候他们会很刁钻,直接把别人的返回结果丢给前端,不重新打包。显然这样很不好,所以返回的json结构一定要清晰。错误码也要统一;有时后台返回的错误有时是几个错误码代表一个场景,比如用户登录失败,后台可能会返回几个这样的错误码:612、606、401等。显然,这些错误可能对应了几个导致未授权访问的情况,比如cookie过期,用户不存在等。这时候前端判断起来比较麻烦,有时后端会额外加一个状态码,有时前端没有通知end做相应的修改,很容易导致页面出现问题。所以我个人考虑了一下。可以增加错误代码的位数。例如加到6,则前4位为错误的主类,后2位为错误的细分保留。这样的未经许可的错误码可以是:400100、400101等,前端对一些错误做一个整体的判断比较好,在后端。定义更多的枚举和常量;这个在其他项目中其实也是一样的,更多的是枚举和常量,然后再搭配Typescript,对于项目的可维护性和稳定性来说意义重大。毕竟字面值是来回复制的,容易出错,而且修改起来比较不方便。封装请求;我们经常会为页面请求写这样的模板代码:functiondoRequest(){this.loading.show();//开始加载动画this.request(url).then(()=>{//业务处理}).catch(()=>{//错误处理}).finally(()=>{this.loading.hide();//关闭加载动画})}请求方式可以是这样:axios.get(url).then((res)=>{if(res.code!=='0000'){returnres.body;}else{//公共错误处理switch(res.code){case'0001'://错误处理break;case'0002'://错误处理break;}thrownewError(res.message);}})好吧,我们来思考一下请求的封装不是所有的请求都需要加载动画,也不是所有的请求都需要默认的错误处理。对于第一个问题,因为有些请求是在用户感知不到的情况下发出的,我们可以增加一个参数来控制加载动画,例如调用时:this.request(url,{slient:true})和doRequest方法,然后:if(!opts.slient){this.loading.show();}axios.get(url).then((res)=>{...}).finally(()=>{if(!opts.slient){this.loading.hide();}})但是这里还会有另一个问题。关于loading动画的关闭,如果loading动画不做任何处理直接关闭,那么当多个需要loading动画的请求并发时,一旦其他请求完成,就会立即关闭。Loading,其实还有其他的请求没有完成,用户还需要等待,所以loading动画需要加一个计数器。当计数器归零时,装载才真正结束。开发时也必须要求。show和close方法必须成对调用。至于另外一个问题:默认的处理逻辑,有时候我们的请求可能需要针对某个错误码做一些特殊的处理,比如登录失败时,公共处理器可能会直接跳转到登录页面,但是某个业务逻辑呢可能需要弹出一个对话框让用户确认跳转。所以:[我们的错误处理逻辑]->公共错误处理逻辑但是如前面代码所示,我们的公共错误处理逻辑是在请求完成后直接添加的,没有机会将我们自己的错误处理逻辑插入公共在处理逻辑前面,一旦有像刚才这样的场景,就比较麻烦了。所以封装的一种方式是使用unhandledrejection事件,因为我们都是使用promise,uncatch的promise拒绝会触发这个事件,所以可以把常见的错误处理逻辑迁移到这里,但是这个事件存在兼容性问题。于是换个思路:this.callRequestWithErrorHandler(this.request().then(()=>{//业务处理}).catch((err)=>{//特殊处理throwerr;//re-Throw公共处理程序}));callRequestWithErrorHandler的实现:functioncallRequestWithErrorHandler(request,opts){if(!opts.slient){this.loading.show();}returnrequest.catch((err)=>{//公共处理逻辑}).finally(()=>{if(!opts.slient){this.loading.close();}})}防止用户触发两次;比如弹出一个对话框,如果是对话框一般不需要马上显示出来,因为用户没有时间做其他操作,但是在某些情况下,比如访问一个界面后显示一个对话框,需要防止用户第二次触发,因为用户有时间做其他的操作可以触发两个对话框显示。如果对话框不是单例,仍然会弹出多个对话框;当然解决方案有很多:比如全局加载,需要遮罩操作,或者在触发按钮上加个标记,防止二次触发。大多数列表背景项都是列表。几乎所有的业务都是从一个列表开始的,那么列表需要注意哪些细节,比如url的查询参数要加上关键字模糊查询,分页之类的参数是的,这样用户跳转的时候到其他页面,也可以返回上一页;所以分页和模糊查询应该先改变url,但是这种情况下,需要能够感知到url的改变,然后重新读取页面和关键字参数,然后触发Request,当进入页面时,参数必须先从url中取出,然后再请求。H5项目用户至上H5的最佳实践和优化建议收集于此。用户退出以确认最近在h5上完成的业务。该页面是一个水疗中心。有这样一种场景,用户需要填写地址。当然,当用户退出表单时,应该给出适当的提示。在Vue中,用户退出确认显然应该在beforeRouteLeave钩子中处理。让我先看看我的第一个处理:beforeRouteLeave(from,to,next){if(!this.dialogVisibled){this.$alert('...').then(()=>{next();}).catch((err)=>{next(false);});this.dialogVisibled=true;}}这个子对话框确实正常显示出来了,用户确认后确实可以返回到之前的页面,感觉功能也完成了。但是问题来了,如果用户在弹出对话框后点击返回,会发生什么情况呢?第一次点击返回后,虽然我们没有调用next方法,但是路由确实发生了变化,变成了上一个页面的路由,再点击返回,会从上一个页面的路由跳转到的路由上一页,甚至直接退出整个页面。显然,这不是我们期望的交互。其实这里主要的问题是前端没有办法阻止历史记录的反向操作,所以接下来这里就很混乱了。一开始我以为只要不调用next方法,路由就不会改变,因为当我们使用next方法的beforeRouteEnter时,如果没有激活是不会改变路由的。如果您知道问题是如何产生的,那么就想办法解决它:beforeRouteLeave(from,to,next){if(this.shouldLeave){next();//离开直接返回;}if(!this.dialogVisibled){this.$alert('...').then(()=>{if(to是前一页){this.$router.back();this.shouldLeave=true;//离开不需要用户确认弹出对话框}else{this.$router.push({name:to.name});this.shouldLeave=true;//用户确认离开不弹出弹出对话框}}).catch((err)=>{});下一个(假);//保持路由不变this.dialogVisibled=true;}else{下一个(假);//保持路由原样}}这里一个shouldLeave用来标记用户是否确认离开,如果为真则直接离开路由,如果为假则弹出对话框,然后立即调用next(false)保持路由不变(虽然点击返回时路由立即改变,触发popstate事件,但是next(false)会立即推回旧路由并保持路由不变),所以现在用户可以'不点击返回退出页面,必须取消或确认。那么问题又来了,真的不能直接退出页面吗?,绝对不是这样的。如果这个页面恰好是路由历史中唯一的一条记录,还是会直接退出页面,因为webview容器中h5的beforeunload事件是没有的。我真的没想到还有别的办法。触发此退出提示。所以刚才的方案只有在二级路由的时候才可行。另一种方案是客户端在用户点击返回时在前端触发回调,只有前端返回true才能退出路由。页面来回滚动多次,部分页面元素会消失。前段时间同事做一个活动页,遇到一个问题,就是页面快速来回滚动后,页面元素(头像,文字等)会消失,这个问题只能在里面解决了只能在移动端转载,不能在PC端转载。这是我第一次遇到这样的问题。首先怀疑这部分元素是被隐藏了还是去掉了,但是vConsole上dom的节点元素都在,而且没有隐藏;好了,继续看是否触发了vue组件的更新打印出来,vue组件根本没有触发更新,所以并没有因为页面更新导致的闪烁。我顿时不知所措。是因为兼容性问题吗?让我们仔细看看。最后在chromedevtool上查看了页面层数,发现这个active的单页里面有多个层,这显然是不合理的。;继续看这些层的触发原因:Reasonswitha"backface-visibility:hidden"style很明显,这些层是由样式属性-webkit-backface-visibility触发的;然后快速找到该元素匹配的样式:*{...-webkit-backface-visibility:hidden;...}大概问题的罪魁祸首已经找到了。我们来看看这种风格的前后效果。经测试,再次没有发现问题。估计是手机浏览器对层数有限制。温故知新,层是什么来的,参考其他博客的文章:浏览器层知识浏览器层知识再简单总结一下,浏览器主要有RenderLayer和GraphicLayer两种;而我们在开发者工具中看到的图层都是GraphicLayer,它们的整个关系逻辑如下:而我们在开发者工具中看到的是GraphicLayer;那么如何触发这些层的生成;触发RenderLayer的主要原因有:(1)普通PaintLayer(SelfPaintingLayers)的生成原因:1.document2.非静态位置属性3.opacity小于14.有cssfilter属性5.有cssmask属性6.cssmix-blend-mod属性7.有csstransform属性8.隐藏backface-visibility9.有css反射属性10.有csscolumn-count属性或column-width属性11.动画改变opacity,transform,过滤器和背景过滤器。(2)OverflowClipPaintLayer:溢出不可见(3)NoPaintLayer:没有需要paint的LayoutObject。其他LayoutObjects与最近的祖先节点共享PaintLayer。所以我们可能经常会因为opacity或者设置absolute/fixed定位和transform属性而触发RenderLayer的生成;而这里我们也可以看到,刚才遇到的backface-visibility属性无疑会触发一个RenderLayer的生成。查看GraphicLayer(1)节点本身。原因1.带有硬件加速属性节点的iframe。如果iframe没有CompositingLayer,则iframe将与父文档共享CompositingLayer。2.Video节点3.Video中的控制条4.3D或硬件加速2Dcanvas节点,getContext('2d')不会升级5.硬件加速插件,如flash6.在高DPI设备中,fixednodes会自动升级为CompositingLayer,因为PaintLayer的升级会改变字体的渲染Mode(在pcchrome上测试固定元素也会升级)7.3dtransform8.backface-visibilityishidden9.Animationoreasingchangesopacity、变换、过滤和背景过滤,并在动画停止时恢复PaintLayer。10.will-change设置为opacity,transform,top,left,bottom,orright。11.positionisfixedorsticky(2)overlapping原因1.如果一个CompositingLayer被overwritten,overlay会自动升级(squashing,overlay升级后的CompositingLayer是从overrided的CompositingLayer派生出来的,两者在同一层级)2.一个CompositingLayer被filter属性的filter部分覆盖(测试没发现有什么升级)3.被transformed元素覆盖(squashing)4.被overflow覆盖:scroll或autonodecoverage5.兄弟节点有animation或easingtochange不透明度、变换、过滤器和背景过滤器。(3)LayerSquashing图层压缩如果有多个PaintLayer与一个CompositingLayer重叠,这些PaintLayer共享一个CompositingLayerbut,有些情况不会共享1.使用mask属性,子节点覆盖与CompositingLayer同级的A父节点。此时子节点squashingWouldBreakPaintOrder的squashingDisallowed不能被A派生的CompositingLayer共享,而是有自己独立的CompositingLayer,即overrider有一个CompositingLayer的祖先节点,被覆盖的会有一个独立于祖先节点的CompositingLayer。2.升级到一个iframe的CompositingLayer不会和任何节点一起压缩squashingLayoutPartIsDisallowed3.带反射的PaintLayer不会和任何节点一起压缩,会独立升级squashingReflectionDisallowed4.当overlay和CompositingLayer不是同一个裁剪容器时,对于例如,CompositingLayer被overflow:hiddennode5包裹。当overlay和CompositingLayer有不同的祖先节点,并且这个祖先节点的不透明度小于16。当overlay和CompositingLayer有不同的祖先节点,并且这个祖先节点有一个过滤器7.当叠加层正在缓动或动画时,结束后恢复压缩。说实话,内容还是蛮多的。好在现在的开发工具都会提示触发该层的原因,可以让我们快速定位到问题所在。