• 《开源框架那些事儿22》:UI框架设计实战


    UI是User Interface的缩写,通常被认为是MVC中View的部分,作用是提供跟人机交互的可视化操作界面。MVC中Model提供内容给UI进行渲染,用户通过UI框架产生响应,一般而言会由控制层调用业务逻辑进行处理,并把处理结果以Model方式返回View,再次渲染。UI框架的大致过程就是如此,按实现方式可以分为RIA和瘦客户端方式,目前基于B/S的瘦客户端方式比较流行。
    UI框架套路上很简单,但是想要做好可就不容易了。目前基于MVC的框架灿若繁星,不客气的说是个软件公司就有自己的技术框架,技术厉害的公司可能还有几套。笔者也经历过多家互联网公司,也用过不少框架,界面开发始终是短板所在。

    一、新UI框架的需求由来


    1. 场景A

    UI框架对View层没有封装或者支持有限。View层的代码全部需要开发人员手动完成,框架没有提供代码封装或者相关代码生成工具,导致项目开发流程中,出现大量Ctrl+C、Crtl+V,进而影响界面代码质量。 
    笔者曾和IT业界比较有名的外包公司合作过项目,一个JSP页面四、五千行代码,Java业务逻辑、html布局和css样式糅合在一起,不要说修改了,就是看明白页面也是异常艰难。跟开发人员沟通了解到,团队采用界面实现方式是jsp,框架提供的样式不满足客户需求,迫使团队自行开发很多框架层面的功能。经常一个需求变动,导致开发人员修改十几处地方,页面的混乱就可想而知了。

    2. 场景B
    技术框架分层严格,界面完全组件化。View层和Model层完全分离,开发人员编写页面需要额外学习组件,访问后台成本巨大,很多业务需求需要通过框架升级方式才能解决,时间浪费严重。
    这是从一个极端走到另一个极端,以严格的框架彻底限制死开发人员。笔者参与过的银行项目,就是采用这种方式:开发人员写的页面全是组件模板,无法灵活定义一些功能,导致全部业务需求完全通过组件体现,而且开发人员本身无法修改组件,组件存在Bug或者无法实现某些业务场景,那开发人员还要在组件团队、项目领导之间扯皮,搞得所有人身心俱疲。

    3. 场景C
    框架性能问题。框架设计人员追求华丽的页面效果,完美的层次封装就容易导致这种后果,特别是在页面复杂或者页面嵌套多时,很容易发生这种问题。
    开发人员最怕遇到这种问题,这意味着项目要伤筋动骨。还以银行项目做例子:原先框架版本是3.0,业务领导认为界面太土,不美观,经过框架团队一年多的努力,框架4.0对界面做了大幅度的美化处理,提供了复杂的封装,结果造成严重的性能问题。因为银行项目页面复杂,有些表单要展示几百个字段,用框架4.0开发后要10多秒,根本无法被接受。最后这一系列的代价由开发人员买单,用html原生语法重新开发这个复杂页面。

    4. 场景D
    升级维护问题。有些框架设计时,设计人员迫于团队或者领导压力,框架集合太多业务逻辑,导致后期团队面临两难选择:不升级,Bug无法解决;升级界面产生各种问题,需要花费大量精力去纠正。
    这种场景也不少见,特别是框架包含多个项目的业务需求时,冲突由为明显,笔者经历过一个项目:每次升级,代码需要对比几百处,浪费2-3天的时间,就这样还不能保证完全正确;最可怕的是,每次升级都要如此。

    归纳一下,对UI框架有如下需求:
    1.提升开发效率,减少重复代码。对于开发人员而言,开发时,界面写得代码越短越好;维护时,修改的地方越少越好。
    2.清楚的UI层次,框架可以分离业务逻辑、布局和样式,让页面结构清晰明了,让开发人员集中更多精力在业务逻辑,而不是页面布局、js、css等技术细节。
    3.丰富的组件支持,项目团队大部分业务场景应该可以被框架组件涵盖,而无需重新开发。
    4.灵活的扩展能力,项目团队可以根据框架规范自行扩展展示组件,实现个性化需求。
    5.优异的性能,UI界面展示时的响应时间应该和原生态语言接近。框架能处理JS、CSS加载及合并的问题。
    6.框架升级不影响业务代码。

    只有满足上述要求的UI框架,对开发人员和项目团队才是真正有帮助的UI框架。

    二、TinyUI的解决方案

     
    Tiny团队对UI框架也有自己的想法,也提出一种UI框架解决方案。TinyUI采用如下设计原则,项目团队在技术选型时需要考虑清楚。

    TinyUI的原则
    1.保证界面开发的灵活性,尊重开发人员的开发权利。 
    Tiny团队遵守“二八”原则,认为再完美的UI框架也只能解决80%实际开发过程中遇到的界面问题,剩下的20%需要开发人员的经验和智慧才能解决。那种提倡一揽子解决全部界面开发问题的UI框架,只会让设计者陷入过多技术细节,导致过度设计。因此,TinyUI提供最大程度的灵活:只要遵守必要的框架规范,开发人员可以自由扩展组件,来满足日益变化的业务需求。
    2.技术问题与业务逻辑分离。
    TinyUI采用开源协议,作为开源软件来提供UI框架,代码本身不包含任何业务逻辑。避免框架设计者开发业务组件,业务团队使用的模式。技术问题由设计者解决,而业务逻辑由业务团队根据自身业务灵活定制、扩展。
    3.性能至上,效率优先。
    TinyUI在设计初期就考虑组件性能问题,目标是接近原生态页面;其次,UI框架完全开源,开发人员只要熟悉模板语言,就可以轻松上手,避免因为学习曲线过高影响开发效率。
    在上述原则的指定下,TinyUI具有如下特性:

    特性
    1.TinyUI是基于JQueryUI,相较于Dojo来说,JQueryUI更容易上手,使用也更简单;相对于ExtUI来说,它更轻量,速度响应也更好;同时它也是可胖可瘦,即可以实现比较大的控件,也可以用少量代码就使得界面更加灵动。
    2.布局与界面分离。TinyUI引入模板引擎,页面采用模板+组件的方式进行渲染,可以大幅简化页面。
    3.采用了多重布局方式,每一级目录当中都可以编写一个布局。项目经理可以根据业务需求按不同层级定义不同的布局。
    4.采用了pagelet方式,可以把页面区块独立提供或者被别的页面进行引用。
    5.提供了UI组件模式,用户可以方便的编写UI组件,并发布到应用当中,同时提供了资源加载机制,如果UI组件包含有JS或CSS文件,不用特别指定,也不用进行首页合并,可直接被使用。
    6.提供了BigPipe机制,可以大大提供模板的处理速度及提升用户界面的使用体验。
    7.通过组件方式开发界面,封装了组件的实现细节,可以平滑切换到其它实现。
    8.与各种开源组件或代码段集成更容易,利用一个已经实现的开源组件封装成UI组件,只要几分钟。接口的扩展可以循序渐进,随用随包装。
    9.资源自发现。框架会自行加载新增加的组件,所有的css/js文件都会自动进行添加。

    TinyUI实际上并不是一个具体的UI展现组件,它只是一个UI构建体系。它可以适应于各种Html+CSS+JS的体系架构中,提供以UI方式开发界面的技术方案。

    组件规范
    TinyUI认为界面所有公用的内容都可以由UI组件包(UIComponents)概括。UI组件包包括一个或者多个组件(UIComponent),UI组件包中包含了其所需的css/js/gif/htm等等各种资源。同时有一个UI组件包描述文件(*.ui.xml),描述对UI组件包的结构、内容、以及对其它UI组件包的依赖关系。
    开发时,UI组件包以独立的maven工程为单位,最后以Jar包为单位进行发布。
    举例:我们要复用JQuery,实际上非常简单。在Maven工程结构中,在resources目录中,放置所有的JQuery资源进来,然后编写一个ui组件包描述文件。UI组件包就算开发完毕了。

    TinyUI框架的组件管理器会根据包描述文件自动加载、引入相关资源,而这些都无需程序人员干预。

    页面规范
    TinyUI推荐采用模板语言,如:tinyTemplate,FreeMaker等作为展现层,这样可以充分发挥组件包的优势。Tiny内部实现复用了tinyTemplate模板语言,但是实际上并没有限制,你完全可以用其它模板语言做同样的事情。

    具体页面开发时,组件开发人员采用宏定义具体的函数接口,普通开发人员直接调用宏接口就可以了。
     
    举例:


     这样写页面多简单,采用Tiny框架的前台开发,基本上帮助你解决了上述难题,但是对你的工作没有任何限制。


    小贴士
    Dojo简介:
    Dojo是一个用javascript语言实现的开源DHTML工具包。利用它的低级API和可兼容的代码,能够写出轻便的、单一风格(复杂)的JavaScript代码,它能提升你的web应用程序可用性、交互能力以及功能上的提高。

    ExtUI简介:
    它是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架。组件丰富,功能强大,可惜现在收费了。


    三、TinyUI的设计思路

    常见问题
    1.UI中JS的引入与顺序,JS合并的问题 。
    2.UI中css的引入与顺序,CSS合并的问题 。
    3.重复代码的问题。同样的内容在许多地方都有,如果要改动就要改动许多个地方,修改时万一遗留了,将来就是一个坑。
    4.整体布局调整困难的问题。
    5.开发效率的问题,项目经理总是期望界面开发越快越好。
    6.执行效率的问题,前台响应要求速度更快。
    7.国际化问题。

         ...... 
     
     下面,笔者依次讲解TinyUI是如何处理上述问题:

    Js和Css的引入与顺序以及合并的问题

    1.JS和CSS的引入问题。
    传统界面开发,程序员需要在页面配置引入js和css的路径,既麻烦又容易出错。TinyUI采用组件封装了JS和CSS,这份工作统一交给组件设计者处理,程序员开发页面时根本不用关心需要引入哪些JS和CSS,只要知道要用哪些宏接口就行了。
    2.JS和CSS的顺序问题。
    这个问题也是传统界面开发之间的难点,JS和CSS之间的顺序搞错,通常会导致页面出现乱七八糟的问题,定位问题也是异常复杂。TinyUI将顺序问题交给组件管理器处理,它会依次检查组件的依赖关系,如果有UI组件的依赖关系配置错误,组件管理器会记录异常日志,这些不正常组件不会被加载到正常组件列表。组件间的依赖关系理顺后,组件内部包含的JS和CSS的关系也就理顺了。TinyUI按如下顺序加载JS和CSS:
    a.  父组件的资源比子组件优先加载。比如组件A依赖组件B,那么框架先加载组件B资源,再加载组件A资源。
    b. 同一组件的资源,顺序靠前的优先加载。组件的js和css路径都可以配置多个,同一组件框架按配置顺序依次加载资源。
    3.JS和CSS的合并问题。
    在传统界面开发,性能调优往往涉及到js和css的合并,页面的IO少了,性能自然能提高,但是对程序员来说这就很困难,改动js和css意味着很多相关页面都要修改。TinyUI从框架层面支持js和css的合并,程序员无需手工调整,框架提供了UiEngineTinyProcessor这个适配器处理上述合并问题。


    UiEngineTinyProcessor合并JS
     a. 适配器通过组件管理器获得全部正常的UI组件,并依次遍历每个组件
     b. 调用组件的getComponentJsArray方法,获得该组件引用的全部js路径,并依次遍历每个路径
     c. 根据路径通过VFS获得js的内容,合并到outputStream
     d. 合并完每个组件的引入JS内容,最后合并该组件需要调用JS代码


    UiEngineTinyProcessor合并CSS
     a. 适配器通过组件管理器获得全部正常的UI组件,并依次遍历每个组件
     b. 调用组件的getComponentCssArray方法,获得该组件引用的全部css路径,并依次遍历每个路径
     c. 根据路径通过VFS获得css的内容,合并到outputStream
     d. 合并完每个组件的引入css内容,最后合并该组件需要调用CSS代码
     
    重复代码的问题

    仔细分析页面重复代码的由来,主要可以分为以下几块:
    1.无用的资源引入。写过页面的人都知道,程序员最喜欢把一段段的js、css到处粘,而不分析是否有用,导致大量不必要的资源引入,降低页面性能。TinyUI通过组件管理器管理资源,最终输出到页面的引用,绝对跟组件包的资源一致,只要组件设计者设计组件包时保证没有引入无用的资源。
    2.重复的业务逻辑。TinyUI采用模板语言渲染页面,通过宏定义解决重复业务逻辑。因为宏是可以嵌套宏的,所以理论上再复杂的页面都可以通过宏搞定,宏如果使用得当,可以大幅度减少页面代码。
    3.相同的功能片段。 有些业务场景需要引入统一功能片段,界面相同,TinyUI可以采用模板命令include完美处理这种场景。
     
    整体布局调整困难的问题

    传统界面开发,页面布局与逻辑代码是混在一起的,特别是复杂页面,调整起来自然困难。TinyUI的设计时一开始就将布局与页面逻辑彻底分成两个文件,当然你要用TinyUI推荐的模板组织。

    Tiny的模板体系组织方式如下:
    ?支持多层文件结构
    ?布局文件统一用.layout扩展名结尾
    ?页面文件统一用.page扩展名结构
    ?只有.page文件可以被外部访问,访问方式有两种.page或.pagelet
    ?访问.pagelet,实际上相当于访问同名page,模板引擎只会做页面渲染;访问.page,模板引擎除了页面渲染还要进行布局渲染
     

    也许有些人看到这里问:将一个文件内容拆分成两块,还要定义两者间的联系,是不是太复杂了?其实一点也不复杂,用户根本不需要定义布局文件和页面文件的联系,请参考以下规范设计你的页面布局结构:

    查找布局文件规则
    1.匹配当前目录同名的布局文件。
    2.匹配当前目录默认的布局文件。
    3.若某一级目录无法满足匹配条件,则向该目录上一级目录进行匹配,直到根目录为止。
     
    比如:aa.page所中的路径是/a/b/c/aa.page,布局的渲染过程如下:

    查找/a/b/c/aa.layout是否存在?如果存在,则渲染,否则查找/a/b/c/default.layout,如果存在,则渲染。

    查找/a/b/aa.layout是否存在?如果存在,则渲染,否则查找/a/b/default.layout,如果存在,则渲染。

    查找/a/aa.layout是否存在?如果存在,则渲染,否则查找/a/default.layout,如果存在,则渲染。

    查找/aa.layout是否存在?如果存在,则渲染,否则查找/default.layout,如果存在,则渲染。

    通过上面的渲染机制,程序员有可能只写了非常少的内容,但是通过分层布局渲染,最后出来的效果也会非常丰富多彩。

    开发效率的问题
    采用TinyUI框架对开发效率提升,至少有如下两点:
    1.重复代码大幅减少,工作效率自然就提升了。
    2.学习曲线降低。采用组件+模板的页面开发模式,对普通开发人员基本可以屏蔽JS和CSS,这两者的技能要求程度就可以大大降低;额外增加的学习成本是模板语言,对程序员而言,模板语言达到使用程度,一天足矣。 
     
    执行效率的问题
    TinyUI框架采用如下技术手段,加快页面的访问速度:
    1.合并页面的JS和CSS。通过减少网络IO请求,特别是在原页面涉及很多资源时,提速效果就会比较明显。
    2.缓冲功能。用户可以设定哪些页面进行缓冲,缓冲多长时间。
    3.支持BigPipe方式渲染页面。前文说过TinyUI框架支持将大页面拆分成一个个pagelet,让浏览器可以多线程同时渲染,从而提升性能。 
     
    普通的web页面,一般来说是页面生成,网络传输,前面页面渲染,这三部分的时间加起来就是操作人员从点击鼠标到最后看到页面的时间。

    举例来说,一个页面有主页面框架,共有4个部分的内容显示。为了便于分析,简化一下模型,假设主页面框架生成需要0.2S,4个部分的内容生成各自需要0.2S,网络传输与浏览器渲染也各计成0.2秒,这样,在传统的方式下,需要的时间就是0.2*5+0.2*5+0.2*5=3秒。

    那么换成BigPipe方式,时间的执行分布大概是如下图:

     

    所以换成BigPipe方式,时间大概就是1.4秒的样子。节省的时间大概是50%强一点的样子。

    当然,这个时间是在各自三段时间都是0.2秒的情况,实际运行过程中,网络传输的时间在局域网中的时间会更快,后台页面的处理,也可以采用多线程处理的方式来进行,这样,后面页面处理时间可以缩短到0.4S,网络传输时间有0.2S也可以了。由于采用了BigPipe方式,在0.6S的时候,就可以看到最页面框架,后面的时间就是一块块出来,当后面出来的时间比较快的时候,给使用的感受就是在0.6S+界面就可以出来。这个与最初的3S,用户体验上明显是有天壤之别的。

    国际化的问题
    对于小项目而言,可能不关心这个问题。但是TinyUI好歹是技术框架,目标是大型项目,如果不能支持国际化,那也说不过去。

    TinyUI提供两种国际化方案,任君选择:
    1.国际化资源标签。国际化资源就很容易理解了,工程添加国际化资源文件,页面用国际化标签进行引用即可。
    2.国际化页面。 国际化页面是指访问某页面时,模板引擎会优先使用与访问者相同的语言的页面文件进行渲染。譬如:存在aa.page,aa.zh_CN.page,如果非zh_CN语言的人来访问,渲染的是aa.page,zh_CN语言的人来访问,渲染的是aa.zh_CN.page。

    最后,偶尔写的累了,也会在http://www.7tie.com上写点心情日记。

  • 相关阅读:
    SQLite剖析之异步IO模式、共享缓存模式和解锁通知
    SQLite剖析之动态内存分配
    SQLite剖析之锁和并发控制
    SQLite剖析之临时文件、内存数据库
    SQLite剖析之数据类型
    关于Docker目录挂载的总结(一)
    docker常用命令
    玩转docker(一)
    go hello
    术语“go”不被识别为cmdlet,函数,脚本文件或可操作程序的名称
  • 原文地址:https://www.cnblogs.com/tiankong102/p/4635125.html
Copyright © 2020-2023  润新知