微信扫描登录
或者
请输入您的邮件地址来登录或者创建帐号
提 交取 消
GITBOOK.CN需要您的浏览器打开cookies设置以支持登录功能

AngularJS框架进阶:如何用Angular写界面

【实录分享】AngularJS框架进阶:如何用Angular写界面

本篇文章整理自汪志成4月11日在『ITA1024前端精英群』里的分享主题:AngularJS框架进阶:如何用Angular写界面。

enter image description here

本期分享嘉宾: 汪志成(Thoughtworks资深前端架构师) 本期特邀内容评审团: 吴子房(网易前端技术专家) 季建(苏宁易购前端开发部门负责人) 汤桂川(广发证券Web前端资深工程师)


今天我们的主题是:深入思考界面、HTML/CSS的语义化、开发时的思维模式、基于Angular的范例,最后,我会在团队协作方面稍作展开。 我了解到,各位基本都有三年以上的实战经验,所以有些概念我不在讲座阶段展开。如果有疑问可以先记下来,我会在提问阶段单独回答。

什么是界面?

我们每天都在写“界面”,有没有深入的想过到底什么是“界面”? 界面,全称是“人机界面”,是在人和电脑之间的分界点,也是软件的设计者和使用者之间的沟通媒介。

所以,如同人和人的交流一样,界面也需要传达三方面的信息:

  • 内容
  • 逻辑
  • 情绪

内容就是你要传达的信息,比如“我有一台Mac”。而一个完整的软件,其内容就是一篇“文章”,不管你怎么排版,它所要传达的核心内容是不变的。

逻辑则是你要传达的内容之间的内在联系,比如“如果你愿意给我5000元,我就把这台Mac转让给你。”,我们看到,这种内在的联系就是商品交换。而软件,最有价值的地方,也就是体现这种逻辑关联。在软件中,这种逻辑关联主要体现为互动和运算。

情绪的载体有很多种,比如口头表达中的用词、语气、语调,而在书面表达中,则主要通过用词、色彩、字号、间距等手段来承载。这些对交流的成果会有显著的影响。

如何实现界面?

无论什么形式的软件,其实都在传达这三方面的信息,Web界面也不例外。 在Web的最初阶段,所有这些信息都通过HTML脚本来表达,甚至都难以准确的表达逻辑部分,后来有了服务器CGI的配合才实现。那个时候的HTML是这样的:

<font color="red" onclick="alert(1)">点我!</font>

这里,内容、逻辑(交互)和情绪(样式)都是通过HTML来表达的。后来,随着界面越来越复杂,这种大一统的html的弊端越来越明显。比如样式难以统一,逻辑无法复用,无论是样式还是逻辑都无法模块化。

于是,从HTML中分出了CSS和JavaScript,相应的,HTML也作废了很多标签,特别是和CSS重合的标签。

所以,现代的界面是这样分工的:

  • 内容:静态内容由HTML表示,动态内容由Js表示
  • 逻辑:由Js表示
  • 样式(情绪):由css表示,html也会表达一部分样式,但是要注意,这只是因为使用方便,实际上所有的html界面类元素都可以通过css定义成其它的样子

实现界面时的三种思维模式

为什么需要不同的思维模式? 为什么我们要定义不同的思维模式呢?

很多人之所以在开发期间觉得费劲,是因为人在同一时刻有很多种不同的目标,这些目标常常存在冲突,以至于你难以在这些目标决策,甚至像《猴子下山》一样频繁切换不同的目标,浪费精力不说,还常常耽误了最终目标。

而通过有意识的使用不同的思维模式,可以在每个阶段都专注于一个特定的目标,避免其他目标的干扰。如果你喜欢读书,可能听说过一本书叫做《六顶思考帽》。本质上,它们是相通的,背后的思想都是“关注点分离”。

在实现一个界面的时候,我们要有三种不同的思维模式:

内容模式

我们只需要把要表达的内容看作一篇文档即可,也就是语义化HTML。比如

<div>Test</div>和<header>Test</header>

在外观上是没区别的,但是语义却有着显著不同,前者我们只知道是一个内容块儿,而后者,不但是一个内容块儿,而且是某个更大内容区的头部。

同样的,假如我们不希望header像它的默认外观一样占一整行,而是和其它文字混排在一起,我们也不需要把它替换为Test,而是可以通过css把它重定义为行内元素。

在内容模式下,我们要让代码尽可能的简化,不要引入无关的内容。比如网站的“面包屑导航”,常常表现为这样:“首页 / 个人中心 / 我的订单”,我们该怎么写呢?我们可以写成

 <ul>
        <li><a href>首页</a></li>
        <li>/</li>
        <li><a href>个人中心</a></li>
        <li>/</li>
        <li>我的订单</li>
     </ul>

但是这里出现的“/”其实并不是必要的 —— 它并不“真”是内容的一部分。于是,我们应该这样写

 <ul>
        <li><a href>首页</a></li>
        <li><a href>个人中心</a></li>
        <li>我的订单</li>
     </ul>

然后再通过css的after伪元素来插入这个“/”。

总之,要记住,在内容模式下,你的核心任务是用html元素写一篇文章,它要尽可能精炼的表达出你要表达的内容,而不要现在就考虑外观 —— 将来你可以通过css把它定义成你想要的样子,必要时甚至可以回过头来改HTML,但不要现在就做! 戴上“内容思考帽”,就是要时刻提醒自己:尽快完成文章,不要管修辞。

逻辑模式

在逻辑模式下,我们的重点在于理清各块儿内容之间的联系,特别是要注意抽象出表象背后的数据关联。比如说,表格的分页和过滤有什么共同点?

enter image description here

enter image description here

初看完全不同,一个是输入框,一个是页标签,但本质上呢?

本质上他们都是过滤 —— 从一个大列表中根据一定条件筛选出一个子列表!而页标签其实就是一个按序号进行的过滤而已。还有轮播,其实本质上也是过滤,它的数据模型是一个环状的列表,列表上有一个数据窗口,位于数据窗口内部的数据用户可以看到,其余的则被隐藏。

   1, 2, [3, 1], 2, 3

这里的方括号表示数据窗口。 在这样的数据模型下,轮播的逻辑就具备了很大的灵活性。它的内容和样式都可以自由布局,而不影响轮播逻辑的正确性。 总之,要记住,在逻辑模式下,我们的主题词是“透过表象看本质”。

还有什么常见的操作也可以抽象成“过滤”呢?

enter image description here

比如“滚屏显示”背后的逻辑是什么呢?那就是根据当前的相对偏移量截取数据。比如对一个10000条数据的大列表进行滚屏显示,那么我们需要把它一次性生成相应的DOM放到页面中吗?

没必要!我们所要做的只是根据滚动条的事件对所要截取的偏移量进行修改,过滤出[offset, offset + viewportSize)之间的数据。 这只是一个常见的例子,实际上逻辑部分的适用范围远比你想象的广阔,而逻辑部分恰恰是前端开发中最容易出错、影响也最大的部分。 留一道思考题:RadioGroup的核心逻辑是什么?有哪些应用场景?

所以,我的建议是:不要去积累控件,而应该去积累这种精炼出来的交互逻辑。只有这种交互逻辑才能适应最广泛的场景。

另一方面,这种精炼的交互逻辑可以非常容易的实现测试驱动开发,保障其代码质量。至少对于这类代码来说,tdd的开发效率其实远比传统的开发方式更高。我在后面的实战阶段会给出范例代码,现在先不用着急。

戴上“逻辑思考帽”,就要时刻提醒自己:抽象数据,梳理关联,理清逻辑,不要被样式和内容所干扰。

样式模式

在样式模式下,我们的目标是对外观进行调整。现在的前端,其外观的复杂度已经远远超出了当年 —— 我们既需要维持统一、协调,也需要各种层次的定制化。在一个庞大的网站中,这并不是一个简单的任务。

所以,我们在写css时,其实是在写程序。我相信你能认同:css的复杂度已经达到了其它程序的级别。显然,我们需要更加重视css的可维护性。

提高程序可维护性的原则对css也同样适用,比如DRY、比如抽象、模块化、局部化等等。 首先,如果你在做需要长期维护的程序,请和切图说再见。

在ThoughtWorks技术雷达中,曾经提到一个“NoPSD运动”。大意是,不要再让设计师画出高保真度的效果图,并切图后让程序员实现它。

而应该让设计师和程序员坐在一起,共同完善设计和实现。除了更快速的响应需求之外,更重要的是它能够形成更好的css架构:设计师有一组设计原则和规划,而程序员可以把它们落实到代码。

设计师的工作步骤是怎样的?先根据客户的既有表单等输入来设计页面的信息架构,然后据此画出线框图。没错,这就是设计师的“内容模式”。然后,设计师会先做一个典型的高保真页面,并跟客户选定风格。这是设计师的“样式模式”,但还只是一部分。

更重要的是,设计师会据此形成一系列的设计准则,比如色调、灰度、间距、字体、字号。这些准则,未必每个设计师都有能力把它们形成文字,但他们至少都在下意识的遵循着。而让程序员和设计师pair,首要目的就是挖掘出这些准则,并且把它们体现在css的架构设计中。

如果程序员和设计师有条件进行持续的pair,那么设计师可以不用再出更多的高保真图(NoPSD),而是直接与程序员pair进行整体设计和细节调整。对于一个精通css的程序员,对设计师的需求实现秒级反馈并不是大问题。

其次,选择一个高级css语言,如sass、less、styl等。原生的css缺乏对变量、模块化等编程特性的支持,它会束缚你的手脚,并降低可维护性。

最后,学会用架构的视角来设计css。css不应该是以单个页面为单位进行实现的,它是一个金字塔:

最底层是一组公共的原则,比如字号、色彩、间距等。比如定义出每个色彩所表达的心理含义是什么,不同的字号适用于什么场景。

中间层是CSS组件,它们具有特定的含义和用途,比如卡片、列表、表格等。设计一套优秀的css组件是相当有难度的工作,幸运的是,我们已经不需要再自己造轮子了。最著名的大概就是Bootstrap了。

最上层是页面级甚至元素级的特例。总有些页面或页面中的元素是特殊的,我们需要对它们进行微调。没错,它正好对应着设计师的思考模型,这不是巧合!

总之,我们应该像写程序一样对css进行分层设计,用架构的视角设计css。

在程序员的样式模式下,我们先要套用css框架,比如bootstrap,但先不要管它的具体样子(比如颜色),只要用对了类就可以了。“用对”的意思是我们遵循框架作者的设计意图来使用它,比如bootstrap文档中的每个组件都写明了其设计意图。

比如:虽然我们确实可以把.well重新定义成.alert的样子,但是,如果我们要表达的本来就是一个提醒,那么就应该用.alert —— 作者设计它就是为了表示提醒。另一方面,如果我们需要一个角标数字,那么我们就应该用.badge —— 即使我们想用的角标既不需要圆角,也不需要背景。如果设计师给出的是一个淡黄的普通提示信息,那么我们应该选择-info后缀,而不是-warning后缀。

这就是所谓“语义化CSS”。设计师将来即使换一个色调,也不会影响这些语义,所以将来需要换成其它类的可能性就比较小。然后,我们进行精细化调整。为了保证和设计师pair的工作效率,这个阶段我们需要专注于样式调整和响应设计师的反馈,而不要被内容和交互逻辑方面的问题干扰 —— 除非它来自设计师。

戴上“样式思考帽”,我们要提醒自己:把响应设计师的需求放在第一位,更要通过顺畅有效的沟通,引导设计师把自己的思考架构清晰的表达出来,变成结构清晰的代码。

实战:基于Angular实现界面

在Angular的支持下,分离这三种模式变得相当简单。如果你急于制作出一个原型,那么可以先从内容模式开始,通过静态的HTML把客户的需求表达出来。

这个阶段,不用追求好看,也不用管数据的内在关联,使用客户提供的样例数据即可,因为建原型的首要目的就是跟用户确认需求,忽略细节,快速确认主干部分 —— 信息架构、页面拆分、流程。

通常,我会使用bootstrap先快速搭建它。Angular工程文件中以路由定义文件和模板文件为主,基本没有控制器的实现代码。

原型建好了之后,就要把静态页面中的动态数据抽象出来 —— 使用控制器初始化它,并且绑定到页面中。现在仍然是“内容模式”,不过做好这一步之后,我们再进入逻辑模式就容易多了。界面元素之间的联系,本质上都是它背后的数据之间的联系。而逻辑模式的目的就是理清这些数据之间的关联。

比如我们在界面上有一个表格和一个分页栏,它们背后的数据,以及这些数据之间的联系是什么呢?

其实它们的背后就是一个数据列表,这个数据列表上有一两个指针,抽象成一个数据窗口。表格关联到的是这个数据列表可见的那部分数据,而分页栏关联到的是这两个指针。

enter image description here

换句话说,这里的数据列表是model,而表格和分页栏是正在observe它的两个视图,虽然它们形态不同,但本质上是相同的。

所以,我们可以把具备分页功能的表格抽象成一个纯粹的逻辑类,给它传入全量数据(items)作为构造参数,然后封装出上一页(prev)、下一页(next)、跳转到某页(goTo)、更改每页条目数(setViewportSize)等操作。

有了这个纯逻辑类,我们的界面实现就有了很大的自由:你可以使用分页栏进行分页,也可以让用户输入页号来分页,还可以不带任何额外界面元素,纯粹靠触控滚屏进行分页(没错,滚屏其实是一种分页,能理解吗?)

同样的,我们也可以把表格换成列表,而不需要改变我们的这个逻辑类。当然,抽象到这个程度,我们的类已经不能只叫作分页类了,它实际上是一个数据视口:DataViewport。 由于它没有任何额外依赖,也没有涉及到DOM,所以我们可以非常简单的用足够的单元测试覆盖它。

接下来,我们把它扩展成什么呢?扩展成轮播逻辑,但与常规的轮播控件不同,我们的轮播允许同时显示多个条目。因为轮播背后的交互逻辑同样是DataViewport!暂停一下,理解这一点。

区别仅仅在于轮播的数据是个环状列表,其他逻辑都是可以复用的。于是,借助交互逻辑的抽象,我们很容易的就把看似完全不同的分页式表格和轮播界面给统一到一起了。 轮播代码如下:

enter image description here enter image description here

它对应的测试代码如下:

enter image description here enter image description here enter image description here

使用的代码(jade)如下:

enter image description here

时间关系写代码的过程我就不演示了,大家回头可以自己读一下。当然,这段代码还可以进一步抽象,来统一分页列表、滚屏和轮播,我留给大家做课后练习,如果想要验证自己的想法可以找我讨论。

特别是你可以在重构的同时把它改写成TypeScript,设计会更漂亮,也更接近传统后端的开发体验。

回顾一下,我们先把界面抽象成一个RingDataView数据结构,为它设计了一些函数,然后我们通过TDD的方式对这些函数进行实现,最后,我们可以把它绑定到形态各异的界面上,而不用再担心它出什么bug。

我们看到,它的健壮性和复用度都很高。而Angular的数据绑定机制让我们只要专心处理数据之间的关联就够了!在一个大型开发组织中,抽象这些交互逻辑类,是很有价值的投资,因为它能适应各种各样的应用场景,而不用担心外观的特殊需求等问题。

逻辑模式结束之后,就是永无止境的外观模式了。

在前面的原型阶段,我们制作了基于Bootstrap版的HTML,到了这个阶段,我们需要让它满足UX的外观要求。这个阶段,我们先要和UX坐在一起,操作着原型再走一遍流程,记录下他/她的意见和想法。

然后,我们需要和UX一起,把设计思路落实到程序,第一步我们先套用他/她选定的色调,这可以通过覆盖Bootstrap(SASS为例)变量,如$brand-primary等来进行整体替换,然后我们可以通过覆盖@font-size-base等对字号进行整体调整,还有诸如间距等调整就不细说了。

接下来,我们需要跟UX一起识别出各种Bootstrap组件,并为其设计几种不同的外观,如为.panel消除圆角,改变色调等。这期间,随着你的反馈,UX可能会意识到自己的设计在统一性等方面存在问题,并继续修改它,必要时也会做出妥协。

通常,前面这几步走下来,UX已经会觉得相当神奇了,因为短短一天的时间,你们就把一个样貌平平的网站做成了符合他/她的基本审美观的网站了。

最后,就是逐页面进行细节的调整,用不了多长时间,你的网站就处于随时可以发布的状态了(达到了普通人的审美标准,但可能离UX的审美标准还有距离)。 剩下的,就是测试了。这期间你会收到来自QA和UX的各种反馈,慢慢调吧。

总结

通过定义不同的工作模式,我们可以有意识的聚焦于不同阶段的目标,排除其他目标的干扰,并有利于实现团队分工。

Q&A

1、问:您好,我想问下,在实际工作中,如何在“样式模式”中去设计整体的CSS排版架构。应用场景是这样的,项目人员写页面,但是需要用到外部的产品,比如:地图包。但是,有时候这个排版样式设计不好,会出现系统的一些事件干扰了地图中的坐标计算。那么这个系统的整体排版样式要怎么考虑,有没有什么规范可以遵循?

汪志成:这个就要把坐标计算的逻辑抽取出来,让系统的事件直接影响这些坐标,而不是具体的元素尺寸等。也就是说,要抽象出一个数据模型,它是一个坐标系,其中有点有线,但是这个坐标系的坐标和屏幕坐标要独立开。

2、问:话说ng2出来了,是否应该丢下ng1?

汪志成:如果你是一个擅长钻研新技术的人,应该直接开始ng2。否则最好等到年底,因为现在ng2马上就要RC了,但是社区还不够繁荣,你不一定能得到及时有效的支持。对于前端新手,特别是从后端转前端的人,纯粹从学习难度上来说,ng2要比ng1小一个数量级。

3、问:大师觉得目前那些组件(或者框架,基础库)算是做的比较好值得学习的?以及它们都好在哪里?

汪志成:我用得不多,算法库首推lodash或underscore,对于理解函数式编程非常重要,而且也非常实用。CSS库首推Bootstrap,特别是团队协作时效果最好。前端框架首推ng2,我看到了很多期待已久的特性,而且对于后端开发人员来说会感觉非常熟悉。然后就是ng1和react了。