面向设计的半封装web组件开发

zhang xinxu

前言

本文内容可谓是对大脑认知的一场洗礼。我们平常提到组件,就会想到重用,各个项目都能使用。而本文的组件,对于某具体项目而言是组件,但是,对于其他项目,就是个半封装的半吊子组件。面向设计、面向项目的web组件开发,就是本文要探讨的主旨。

一、人与组件

目前这个阶段,我们所使用的web组件都是人所编写的,因此,人这个个体在赋予组件生命的时候就扮演了至关重要的角色。人的技能背景,个人喜好,甚至世界观都可以从组件中得到体现。但人无完人,因此要想诞生一款完美的组件,需要上下游很多优秀的同事,把自己最棒的世界融入进去,协作完成,其中下面这4个职位参与度较高:

与web组件开发联系紧密的4个职位

注意到了没有,前端开发人员处于web组件开发流程的下游;这其实没什么,大家通力协作,组件弄好,项目做好,产品做好,皆大欢喜。然而实际上,由于大学没有前端专业,因此负责团队web组件开发的,大多是从与组件几乎不相干的后台开发转过来的,或带有明显的后台开发烙印的。也就是说,前端开发不仅处于下游,还有1/3的身子在web组件之外。

前端开发的背景

一个web组件的诞生,理想状态应该是需要设计、UI工程师、前端开发通力协作完成的。有句话怎么说来着,理想很丰满,现实很骨感。

现实是:大多数web组件都是前端开发人员一个或数人,按照业界约定俗成的套路实现的,超前于UI设计,游离于UI重构。什么意思呢?比方说一个弹出层组件,有标题、关闭按钮、主内容区域,底部确定/关闭按钮。这套结构是业界约定俗成的,开发人员在写这些组件的时候,会发挥自己的聪明才智,好好地封装,好好地设计API, 目的就是可以不用关心以后设计师设计的弹出层长什么样子,因为我预留了类名接口,UI工程师换个皮肤就好了;同时能保证组件的整站复用。也就是项目开始之前,成熟、封装良好、API丰富的web组件就已存在。

亲们,读到现在,你们是不是觉得没什么问题,挺好的啊!?对的,在iPhone出现之前,大家都觉得Nokia挺好的。

从人的角度讲,这种看似牛逼的路数最大的问题就是无视了分工,所谓术业有专攻,闻道有先后,开发再逆天,也是存在局限的,或者说是人性上很难避免的东西。

1. 位置与角色

大家应该都知道,设计师和程序员处于两个不同世界的,因此,当其中一个角色独自承担另外一个角色工作的时候,自然会存在诸多阻碍,其中很重要的一点就是设计重心。

前端开发写web组件,实际上也是一种代码的设计,此时,这些程序员气质浓郁的前端开发人员设计的重点自然是放在组件功能、通用性、扩展性以及易用性上,毕竟这一块是自己擅长的,至于UI,会预留API或其他接口,也能满足常规的设计需求。

如果交互设计师或UI设计师来写web组件, UI和交互可定制则是其设计的重心,发展得好的话,可以直接成为另外一种流派,直接和(开发背景前端的)面向功能的web组件流派相抗衡。但是,国内的现状是,设计师一般不参与直接的代码建设。

实际上,从职位上划分,UI开发应该是最适应构建web组件的角色,能兼顾设计之美,重构之美,以及代码之美。只可惜由于门槛低,水平良莠不齐,尤其JS方面的驾驭能力有限,于是,最后的结果是,组件的建设都落在了工程气息浓重的开发身上。

可以看到,虽然不合理,但是情非得已。有人可能要叫了:“啰哩吧嗦,狗皮膏药涂到现在,到底哪里不合理,你到是说啊!”

OK, 稍安勿躁。由于组件这东西只能开发实现,导致在组件的设计以及使用上,前端开发有了非常大的话语权,下游决定了上游,设计师以及CSSer在组件眼中就是个摆设。比方说设计师对dialog弹框进行了某些微创新,比方说下面这样的(无标题无关闭大背景色块):

一个提示框效果示意

去问开发可行性,结果,开发来了一句:“哎呀,这个功能我们的弹框组件目前不支持!”我相信这种场景很多同学都遇到过吧~最后,基本上都是设计师妥协,使用传统弹框交互或布局。所以,坊间才有“苦逼的设计师”的传闻。

苦逼的设计师这样苦逼地工作了很多年,还好,大家就这么过来了。然而,事物是不断发展的,技术是不断进步的,随着CSS3, SVG等现代web技术日趋成熟,我们在UI展现层能够做的事件就非常多,更新变化也更加快。在这种大环境背景下,还让开发来决定设计,显然这对产品是不负责任的。其他竞品在组件UI细节上不断闪现人性化、情感化的创新之处,交互也更加流畅与舒适;而你还抱着“我们这是成熟的弹框组件,不能随便改”的想法固步自封,使用"duang duang duang"生硬,机械,呆板的弹框组件,注定在现代web浪潮中被冲到沙滩上。

对于页面工程师,切图仔而言,其实也挺苦逼的。在部门没啥地位的页面仔们,苦学了一种新的布局,比传统实现少嵌套2层标签,且满足各种场景需求,好棒! 结果交接的开发来了句“我们的选项卡组件不支持你这种结构,你需要调整下”。

具体项目的具体组件本来就应该是大家通力协作完成的,现在就因“组件是我写的,我就是发言人”的心态限制设计、重构的创造力。这就是不合理之处。

2. 扬长避短

组件的作用是什么呢?就是“偷懒”,来个新页面,“啪”一套;来个新项目,也是“啪”一套。功能棒棒哒,任务完成,老婆再也不用担心我加班晚归啦!

偷什么懒呢?主要是偷自己不擅长那块的懒。这是人之常情。设计师不懂代码,他希望的组件是,直接可视化操作就OK的;页面制作人员,他希望的组件是JS功能那块它不需要操心;前端开发们,则希望组件HTML布局那块不需要关心。

好了,现在组件都是开发背景的前端写的,所以呢,很多HTML布局啊,样式表现啊,都集成在了web组件中,一些动态的表现就使用洋洋洒洒的API控制。于是,开发使用的时候,只要按照特定的套路,自己不需要去写页面之类的,组件就棒棒哒了!

这样的例子很多的,比方说知名开源项目kissy中的选项卡组件,根据我的观察,其选项卡要么是要通过JS脚本动态创建,任何特异化的需求都是通过丰富的API接口或者回调实现的。总之,组件的使用只需要玩弄JS, 不需要把玩HTML; 要么就是套用固定的HTML结构以及className值。这比较符合JS造诣很深的前端开发的偷懒模式。

等等,篇幅原因,就不一一举例。

这并没有什么对错之分。有的企业就是功能为王,业务导向;有的企业就是产品为王,体验优先。不同的企业文化,不同的产品要求,不同的团队规模与技术能力,决定了web组件的表现形态、使用方式等。只要符合企业的生产与成长需要,都是很棒的web组件。因此,类似kissy这样的模块集合,是否适用于单兵作战的中小企业以及注重用户体验的创业团队,就需要执行者好好斟酌斟酌了!说不定,jQuery UI可能更合适一点。

至少对于我而言,绝不会去使用对HTML做过多限制的web组件的(例如要求节点必须如何如何...):

KISSY 1.4 srcNode 初始化组件时必须要求内容节点必须包含类名 ks-overlay-content (这里 ks- 为 prefixCls)...

“扬长避短”原本是个褒义词,显然,我放在这里讲,是要说其不好的。有句话讲的好,叫做术业有专攻。前端开发都会HTML/CSS, 毕竟相比程序开发,门槛很低,在相关领域也浸染了些年月,构建web组件的时候,感觉HTML/CSS方面的应用也是比较顺手的,功能也都能跑起来。但是,在HTML/CSS方面造诣很深的开发看来,前端开发web组件的HTML结构等设计不敢恭维。举个真实的例子:

要实现一个相对文本框定位的Autocomplete组件(相比绝对定位, resize时候无需JS重定位),前端开发是这么实现的,在<input>文本框外面wrap包裹了一层<div>标签,设置positionrelative, 然后Autocomplete主列表容器就在这个包裹<div>里面,绝对定位于该<div>, left/top值与文本框尺寸关联。

相对定位弹框实现反例演示

最后的实现,效果是有的,兼容性也是可以的,在前端开发看来应该是OK的,自我满意的。但是,在我看来是相当糟糕的!

这外面包裹的一层<div>标签完全是多余的,我们无需包括标签就能实现我们想要的相对文本框定位效果,而且兼容IE6+. 目前的实现,这个包裹的<div>看上去貌似起到了辅助剂的作用,实际上留下了超级隐患,让组件生命力(适用性)立马降低50%。

小隐患是,可能相邻父子选择器会失效,比方说.parent > input选择器,会因为莫名其妙爸爸变爷爷导致文本框样式失效,这个问题嘛,改改CSS就可以了;以及多了一个relative, 元素的z-index层级管理又更复杂了一步,维护成本增加了!

超级大隐患是,Autocomplete组件下拉框高度超出容器高度是很常见的,但是,由于你外层有了一个position:relative的父级,只要外面任意一个容器有overflow:hidden,你的Autocomplete下拉就会被拦腰斩断,部分内容不可见;如果是overflow:auto/scroll,则会出现讨厌的滚动条。这个组件基本上就废了!但是,如果没有外面这层讨厌的position:relative父级,overflow:hidden就干不掉下拉列表,Autocomplete组件就能长青不老,适用性提升了整整一个量级。

上面截图,仔细看会发现有个*-placeholder<label>元素,先不说这个label元素没有for属性,要知道,企业产品近8成用户的浏览器都支持原生placeholder(包括颜色等自定义),对于这部分浏览器,我实在想不出任何需要自定义实现placeholder效果的理由。最大的可能性是前端开发童鞋不知道目前凡是支持placeholder属性的浏览器都可以CSS自定义吧。也就是前端开发本身的技术深度以及视野的局限。

我们对上面的絮叨总结下,就是想表达,人的局限性带来了web组件的局限性,唯有相互协作,告别单职挑大梁,才能让web组件有所突破。但是,目前这个阶段,这现实吗?优秀的前端如此匮乏,尤其能够上下游承接的,想寻求突破,我们是不是可以从别的地方入手?

二、组件的抽象与封装

我们在写web组件的时候,我们总是会尽可能地去抽象功能,封装API。 于是,当我们使用这些组件的时候,我们只需要针对一些常用API去做设置,就能满足我们日常的功能。

在很长一段时间里,我一直都是这么做的,业界几乎所有开源的web组件,包括团队内的web组件都是这样的设计思路。

然而,随着时间推移,在各种类型的项目中浸泡,我发现,尽可能地去通过JS的手段封装组件似乎不是未来的趋势,反而可能会成为一种制约。

1. 现代web技术发展

CSS3/HTML5等现代web技术不断发展,以及IE6浏览器的淘汰。浏览器自身能够完成的事情要比以前多很多很多。举个极端的例子(都是厂内现有的真实案例),模拟单复选框的web组件,如果按照传统的组件开发模式,API这块(events回调API缺省)可能就会是下面这样子:

一些API参数等

从代码的角度讲,还是很美的,对吧;功能上也是可以的,各类需求也能满足;API的抽象与封装也是没有不足。只是,恕我愚钝,这洋洋洒洒600+行的JS代码的价值在哪里?在IE9+浏览器下,单复选框的自定义效果是不需要一丁点一丝丝JS代码的,对于IE7-IE8浏览器,我们只需要关心input本身,全局委托click, 让单复选框input的类名和checked状态保持一致就可以了,其他的交给CSS选择器完成就好了。全部的代码量跟上面截图API参数代码量基本上一样,而且对设计师和页面重构人员更加友好。

工程化的思维方式,高度的逻辑思维与抽象能力,以及造物者的美妙感觉,似乎让开发热衷于设计与封装API, 熟不知,当新技术出现,或者发现有其他更简单的实现的时候。那些所谓的封装是显得那么的笨重与鸡肋。

“好了,你别说了,我改还不行吗?”组件这东西,由小变大很简单,可以向前兼容;你想从大变小,我只能呵呵。还是等下一个全新的版本,或者其他完全不相干的项目了,兄弟!

2. 变幻莫测的UI层

现代web技术的发展,让我们在UI表现上可以有更多的选择。这对web组件UI这块的抽象与封装带来了很大的挑战,来看看一些同行的言论吧:

关于UI逻辑抽象的言论

如果有组件自信对UI层进行了完美地抽象与封装,这表明,这个组件已经对UI层的表现有了很大的限制。这是毋庸置疑的,有些事物本身就是对立的矛盾体,工程化就是要讲求一致性,但是,个性化显然就是需要不一致,而UI表现就是一件很个性化的事情。So, ...

当前的web组件面对新项目新的UI表现需求,采用的做法要么就是new一个新的封装进行二次封装,要么多些写代码做冗余处理。恩,都不是什么好做法,不过又没有办法。

3. 跨部门的合作

最新,我们完成了一个项目。这个项目质量非常高,无论是UI, 交互和体验,各方的评价也很好。后来我们要开始一个新的且比较大的项目,就希望把已有项目很多好的东西借鉴过来。设计还是同样的一批设计师,但是,前端团队却换了一拨人。理想的状态应该是这样的,新项目的前端团队,直接使用之前项目这边的前端UI组件(除了颜色,尺寸什么的都是一模一样的),less的变量文件颜色一改,分分钟无缝转移,多棒啊!

但是,最后的结果是,新的前端团队放弃了之前项目的前端解决方案,还是使用了自己的简洁派做法,seajs + jQuery + ...

各位观众可能会疑问了,咦,为啥不使用现成的东西啊?主要原因就是上手成本和学习成本,企业这边的前端组件体系可以和kissy一拼了,面向对象、模块化、按需加载,API封装也是尽善尽美,走Grunt.js玩nodejs, 前后端分离,走得还挺超前的。但是,自己团队内部新人有人带,有人教,可以慢慢上手。但是,你这一套东西交给其他团队,考虑到其量级以及复杂度,以及项目的紧急程度;一看到你那洋洋洒洒N多的API参数,着实让人望而生畏。以及还有不可忽略的代码风格、模式等差异。

为什么会有那么多API?

这个问题很有意思:“为什么会有那么多API?” 这个问题可谓是问到点子上了。原本组件诞生的时候,API是没有这么多的,后来,现有的API不能满足某些UI层或交互层的使用场景,于是,前端开发就抽象一下,新增一个API, 满足这个需求。但是呢,这项目啊是一个接一个,需求呢,也是千变万化。没过多久,又来了个需求,开发据理力争,还是没能挡掉,结果,又在组件里新增一个API。 久而久之,就有了现在看到了很多API参数,此时,这个web组件就可以称为“成熟的web组件”,对外宣传“适合多种应用场景”。这其实很有意思,所谓的适应性居然是通过增加各种API,增加组件代码量实现的,然后还自我得意一番。我开始有些明白为啥小小的单选框模拟组件居然需要600多行的JS代码了。

归根结底,还是“尽可能对组件API进行抽象和封装”这样组件编写意识导致的,这种想法和意识实际上已经不符合发展的趋势了。

我们看来有必要转换思维。

三、转换思维,分离与半封装

大家都知道YUI已经停止维护有段时间,至于原因,不知道大家有没有好好体味下(参见下图言论):

关于YUI终止开发的言论

正如我所言,为了满足UI需求,讲求封装的设计理念,必然会导致web组件越来越大,越来越臃肿;同时,前端开发这个角色由于关注点和职位跨度原因,对于UI层的处理能力和其对JS语言本身的处理能力还是有不小差距的。

因此,考虑到未来发展,我们必须做出适当的改变。我个人觉得可以从这两方面寻求改变:分离半封装

1. 分离

实际上,目前的UI组件也是有分离的,很多样式都可以通过类名控制。然而,这些分离都是逼不得已的,比方说Autocomplete的列表样式,你总不可能每个列表都内联一段样式相同的style设置吧。但是,如果条件允许,样式控制都是尽可能在JS中完成的,例如Autocomplete的列表容器的尺寸、定位、层级等等;甚至有些组件直接内嵌完整的CSS代码。

这样的设计是奔着足够封装,调用方便去的。但是对UI的友好程度,恐怕就不是设计者的关注点了。

显然,这里要讲的“分离”要比传统的“不得已分离”要更进一步。包括两方面:

①. 样式控制从JS分离

即只要是静态样式控制,全部交给外部的CSS来完成。甚至包括组件的状态控制(尤其是只要支持IE9+的项目),例如显示,隐藏等等。

拿经典的弹框组件举例。至于absolute定位,还是fixed定位,背景色是黑色还是其他,透明度多少,弹框是否居中定位等等,组件都不管(目前这类全都JS API控制),组件要关注的是功能层面的东西。千万不要自己为是,使用JS计算让弹框居中显示(外带resize重计算)。UI需求不是你可控的,比方说我们这边几个项目的弹框的需求是上下2:3滚动跟随显示,CSS完全可以实现此效果,CSS才是最好的UI样式API。

说到这里,忍不住爆料下,由于各种错综复杂的原因,项目的弹框组件Overlay的透明度和颜色API目前是个酱油,自定义没效果。于是,最后还是使用CSS的!important重置了JS的style控制。如果当初全都交给CSS, 什么事都没有,还省了一大波JS代码,呵呵呵!

CSS重置组件样式示意

②. 参数来源从JS分离

举个最简单的例子,文本框/文本域的placeholder占位符效果组件,其占位符内容通过JS的{ placeholder: '' }参数赋值吗?很显然,要优先使用HTML元素上对应的属性值。这其实是很基本的常识,然而,很多组件只有JS API这一个入口。

或者tips组件的提示内容源自元素的title属性等。

即符合语义,页面工程师还可控。一举多得。

以上的分离,省JS代码是小,更大的意义在于释放了UI设计师以及UI工程师的潜力,使上下游所有的成员角色都参与到web组件的建设中来,只要这个设计师的设计能力>开发,页面重构人员的重构能力>开发,这个组件的品质,绝对就会比前端开发一个人完成的质量要高。而且,对于其他不同UI风格的项目具有更强的适应性。

2. 半封装

一提到web组件,我们很自然跟封装联系在一起。对啊,你不封装怎么叫做组件呢?现在,我们有必要转换思维了,要想满足日益高涨的UI层需求,同时组件不会日趋臃肿,我们就需要对组件进行半封装。

那问题来了,什么是“半封装”呢?

所谓“半封装”是指我们只封装语言层面以及功能层面的东西;对于UI层,虽然也是JS, 但我们按照CSS的思维处理,以满足当前项目UI需求为主,不讲求封装以及API设计,保持随时的激活态,无论是UI工程师或者前端开发都可以根据具体项目具体需求调整之。

需要注意的是,此“半封装”是针对不同设计风格的项目而言,对于某一个具体项目,其web组件还是完全封装的,还是有成熟的API接口的,小白开发也是可以直接使用的,只是不能直接应用到其他项目!

分离,和半封装使用示意图表示就是:

web组件分离和半封装示意

对应的职位参与如下:

web组件分离与职位参与

从上图可以看出,我在第一章节大肆分析的当前web组件分工不合理性(见下图gif)得到了很好地解决。web组件样式控制CSS化,以及非封装的HTML结构、类名等大大提高了偏设计的前端对组件建设的参与度,对组件设计品质的提升,非常有帮助。

过去的组件开发和分工示意

关于web组件的半封装性

对于开发思想浓郁的程序员,可能有些无法接受这所谓的“半封装”思想。我cow, 每来一个项目,都要对组件进行一番修整,人力成本怎么算?如果支援10个项目,就有10个子组件,日后维护怎么办?

关于人力成本:
传统的web组件是由前端开发在原组件基础上二次封装成项目需要的web组件,这个成本,跟直接动组件代码中的小部分UI层内容,实际上没什么区别的,这个下一章节再次提到;

关于维护成本:
首先对于每一个具体项目而言,组件都是通用的,而半封装,只是对其他项目而言是半封装,对于具体项目依然是个全封装的web组件,跟传统web组件维护成本是一模一样的;

其次,半封装的web组件,非封装的部分是多变的UI层,整体结构依然是面向对象开发,其他部分依然使模块化加载,因此,看上去10个子组件,实际上仅仅是10个跟实际项目紧密契合的UI布局相关的子组件。要知道,UI层本来就应该具有特异性,独立开来反而降低了样式调整带来冲突的可能性。

最后,对于不同项目而言,组件本身就应该是独立不耦合、相互无关联的。比方说jQuery, 10个项目都使用jQuery, 有个项目需要使用jQuery某新特性,于是升级都爱了2.0, 其他项目也要更着升级吗?显然是不推荐的,对吧,新版本的浏览器兼容性可能不一样,一些老的API可能删除了。jQuery况且如此,我们实际开发时候,10个项目实际上就是10个组件体系,别看貌似代码都是一样的,维护起来还是要独立维护。

因此,综上所述,根本就不存在增加日后的维护成本。

半封装web组件唯一的问题在于,对小白从业人员不友好。很多新人就是大自然的代码搬运工,就是希望组件足够傻瓜化,最好直接引入一个JS文件,自己想要的功能就出现了。这就是为何我们平时在互联网上看到的开源的web组件都是封装很良好的原因,你使用麻烦,哪个小白会使用!同时,因为需要考虑各种应用场景,API很多,组件很重,为了宣传,只能美名曰强大、适用于各种场景。正如无招胜有招,没有使用场景才是最能适用于各种场景。

我们在企业打工吃饭,首先考虑的应该是组件本身的质量、产品和项目的质量,至于开源什么的,是不是想太远了。

我们有专业且可爱的同事们,为何要把合作的同事当小白,而不是不发挥各自的特长,一起把产品弄得棒棒哒!?

举例补刀

就在我写到这个位置的时候,高级视觉设计师J向我咨询日期时间选择组件的时候,出现了这么一句话:

设计师、开发与组件

网上能够找到的日期时间选择web组件都是封装良好的web组件,对于对设计没要求的小白用户或者一些小厂很友好,但是,对于对设计很重视的使用场景,所谓的“完全封装”反而成为了一种制约,且不说组件的class类名风格是否符合团队规范。

其实,我们希望模块化或者组件化的东西是日期筛选或者排列的算法。对于UI层,你大肆集成,一了百了。最后的结果呢?被设计师嫌弃了!其实呢,里面的HTML构建也被HTML工程师嫌弃了!后来,设计师委曲求全接受了一款时间选择器组件,结果还不支持IE7浏览器,真是造化弄人啊!

正在阅读的你,遇到这种情况你会怎么办?试想下,如果我们有一个半封装的web组件,UI层内容和模块层内容清晰且独立,此时,UI工程师就可以专心捣鼓UI层内容,可以满足设计师任何挑剔的需求,岂不美哉?!

四、面向设计高级定制的产品组件

怎样的产品才高端,有档次,脱颖而出?

我们可以拿衣服为样板,回答上面这个问题。

很显然,私人定制的衣服要比流水线上生产的衣服,要更有档次,产品品质要更高。

完全封装的web组件,就好比流水线上的衣服,讲求工业化,效率以及成本。但是,这样的web组件做出来的产品的档次和质量,未免有粗糙之感。而本文所反复推崇的半封装的web组件,非常重视量体裁衣的设计、量身定制的交互,使得我们的web站点犹如私人订制的高级礼服。在芸芸产品中脱颖而出,赢得口碑。

需要明白的是,私人订制并意味着完全放弃工业化、模板化的东西。就算你让玉兰为你设计高档礼服,礼服的制作过程中,还是会用到线稿、样板等等。只是并非完全的流水化。面向设计的半封装的web组件也是这么回事,我们还是需要面向对象、需要模块化加载、通用的事件处理等等,只是这些成为了背后的一部分而不是全部。

下面我们一起来看一个很有意思的问题,初期开发成本问题。

面对设计的要求,我们以前的做法是这样的,web组件不动,针对具体项目,common.js中来个二次封装;而现在的做法是采用私人定制的方法,在原web组件根据设计需求,捣鼓UI相关内容。差异见下示意图:

两种组件模式的前期工作

都是需要为了满足UI的需求做一些工作的,所以前期工作成本并不好评估。但是,可以肯定的是,JS的代码量下降了很多,且对于设计对于UI的定制性更强了,如果团队有很多厉害的设计师,这个web组件的品质就牛大了。

个性化设计与工业化生产的矛盾

每个企业应该都有自己重视的产品,而且都希望这个产品各方面品质都高人一等,对,是各方面品质,这样才有竞争力,我们腾讯更是看重产品品质。

我们有很多顶尖的产品经理和交互/视觉设计师,设计的web控件既美观又大气、而且还亲近用户,细节上很多微创新,高品质的产品有个产出的前提,但也只限于前提,最后的呈现还是需要开发来实现的。

可见,开发同学在产品品质的把握上是多么的重要。

每个IT企业里面都有一个或一群开发,他们希望自己的代码可以改变世界,同时吸引软萌妹子;他们希望通过代码生产工具以及类似可复用的东西,解放生产力,提高效率;他们希望自己的代码功能棒棒无bug, 性能卓越无匹敌。

正如之前讲过的,既是优点也是局限。重复利用的天性刻在了骨子里,虽然中心明白设计很重要,但是,对于设计效果的实现,总会让设计师苦笑不得,为何?

因为这本身就是矛盾的,优秀的开发讲求代码复用、性能,然而个性化的设计就是要与众不同,避免复用。所以,各位开发同学,如果你的团队设计很优秀(如果设计跟你一个水平,忽略这句话), 为了产品的高品质产出,我们必须有强烈的面向设计的开发思想,首先第一点就是心理上要能够接受,各个项目之间基本上没法重用的web组件;其次,行动上,愿意花一些额外的功夫和心思,雕琢设计的细节实现。

等最终高品质的产品出来了,内心会很充实会自豪,你会发现自己有了长足的成长,设计师MM似乎对你更友善,跟你说的话也比以前多了。

举例与补刀

就在上个月刚刚完成了一个取色器的定制,这里来分享下哪些是需要高级定制的,哪些是按照模块化使用的?首先下面这个是设计图:

取色器面板截图

首先,这个取色器特异性非常强,网上绝对找不出第二个取色器组件长这个样子。所以,崇尚自然搬运工的,可以洗洗先睡了。虽然网上有很多取色器组件,我看过他们的实现,不得不说,真不怎么样(直接搞一张渐变图片是要闹怎样)。所以,为了还原设计品质和保证组件代码质量,反而是高级定制来得简单快捷。

首先,我们要分离UI内容和组件模块内容。模块内容包括:Dropdown组件(含边界判断),颜色转换模块(HSL, RGB, HEX格式间颜色转换);剩下就是UI层内容,这是需要私人定制的。没错,这个组件的定制工作量比一般的组件要大很多。从代码量来看,要五五开。

再次强调下,大家一定要摒弃“web组件完成后尽量不要动里面代码”这样过时的想法,为了满足设计,额外的成本付出还是需要的,只是以前只在外面小打小闹,现在是内部调整。

至于如何具体分工,因团队结构个每个人擅长的领域不同而不同。

好,这里,有必要加粗说一下:恰如礼服定制需要技术娴熟的制衣工人一样,web组件如果想走面向设计的高级定制策略,团队里一定要有类似的技术娴熟的前端人员,否则会有相当高昂不划算的定制成本。

五、实践出真知

近2个月之后……

前面提到过设计师jerry对日期时间选择组件的咨询,后来,莫名飞来一些蝴蝶,这个日期时间选择组件就我来实现了,基于面向设计的半封装理念,我是怎么实现的呢?

  1. API分离 - 回归UI组件的本源 - HTML
    大家都知道,HTML5里面,本身就是有类似date, datetime类型的input控件的,如Chrome浏览器下会自动调用自己的时间选择控件:Chrome浏览器下的时间选择面板所谓UI组件,无非就是实现满足产品气质的统一兼容的浏览器的那套东西。道家讲求追本溯源,从这个哲学层面讲,使用浏览器以及HTML5规范那套交互体验,一定是最佳实践。传统的洋洋洒洒的JS和五花八门的API都是逆时代而行,注定要成为历史的。因此,我这里实现的日期时间选择组件基于原生的规范的HTML5 input元素实现,没错,只能是input元素。具体包括:

    <input type="date">
    <input type="year">
    <input type="month">
    <input type="hour">
    <input type="time">
    <input type="minute">
    <input type="date-range">

    每种type列表对应一种时间选择面板。例如,选年的,和选月的(终效果截图):

    年份选择面板 月份选择面板

    不仅仅如此,可选时间范围,甚至包括时间选择的跨度均是通过HTML原生属性设置完成。包括:min, max, step等。

    <input type="date" min="2015-07-03" max="2015-07-28">
    <input type="hour" value="09:20" min="8" max="20" step="2">

    min对应于传统日期组件的startDate API, max对应于endDate, value对应于initDate

    而这里,我们将类别交给type, 范围交给min/max, 从JS分离出来,交给了HTML. 即增加了组件的可读性和可访问性,同时,给我们的组件瘦身,可以专注于呈现本身。

    最终,我们的组件API就非常的简练,只需要一个onShow()/onHide()回调,使用也更加规范(因为遵循的HTML5规范),记忆与学习成本也更低。最最厉害之处在于,我们只要在common.js全局实例化一下,例如:

    $('input').each(function() {
        var type = $(this).attr('type');
        if (/^date|year|month|time|hour|minute|date\-range$/.test(type)) {
            new DateTime($(this));
        }
    });

    其他所有子页面,你都不需要额外的JS, 设置额外的API。 只要按照HTML5规范,写好HTML内容,日期时间选择功能就杠杠地呈现在那里。

    工作不要太轻松哦!

    什么?选择日期的回调?

    用户只要关心input元素本身就好了,就跟使用原生的input元素一样,给其绑定change事件,当文本框中日期变化的时候,change事件就会自动触发。可以说,就算没有这个UI组件,我们的页面功能也是可以正常使用,这就是API分离给HTML在可访问性上的质的提升。

  2. 面向UI设计的高级定制
    UI层的东西变化性非常强,你不可能说是写了个组件,适用于所有团队,所有场景,那你的组件该有多大,多少逻辑判断哈!对于某一个项目而言,是不需要的,我们需要稳固的核心,以及在这个基础上,面向UI的高级定制。日期时间选择比较难实现的一个面板就是日期范围选择,类似这样:日期范围选择面板其实呢,要实现有此功能并且功能通用的组件,并不难,我们从网上找一下,也是可以找到的。但是呢,如果你看了设计师对选中细节的处理,你去找个可以满足如此UI设计的组件看看,就很难了!那些细节呢?包括:

    1. 独立选中真空;
    2. 非首尾选中实色;
    3. 首尾选中半直角半圆角;
    4. 首尾选中处于边缘且折行则顶到边缘且半开放;
    5. 首尾选中处于边缘不折行则不顶到边缘;
    6. 正常区域方方正正,选中顶到边缘。
      正常区域和边缘区域
    7. ...

    咋整?显然,面对如此UI,我们需要对组件内部实现进行简单的定制,主要定制内容包括:

    1. 通常态类名
    2. 选中态类名
    3. 选中起始类名
    4. 选中结束类名
    5. 月初类名
    6. 月末类名
    7. 星期几对应列类名
    于是,极端情况,这么多类名会同时出现:
    多类名同时出现的极端情况示意

    如果我们是一个普通的日期选择器,只要两个类名就可以了:.ui_date_item.selected。但是,当我们面对的UI组件的样式非常精致的时候,我们就必须根据设计量量身定制一些类名,辅助实现我们的表现层效果。这样的成本是必须也是必要的。

    提问?我们还需要对国家法定节假日(包括周六周日)做处理吗?

    从组件本身强大角度讲,支持自然显得更牛,但实际上这只是开发人员自己的感觉,现代组件讲求轻便灵活,如果项目没有这样的需求,我们就没有必要做类似的处理;如果后期提出了需求,我们再处理之。

    这种思维方式有别于传统的——“我需要考虑好各种使用场景,弄一个功能丰富强大的组件”;而是“UI层需要什么,我代码就做什么,尽量减少API,只考虑常用场景,生僻场景置之。”

  3. 模块化组件部分
    组件体系通用的定位模块。时间选择器内部每种类别的面板也是独立模块,分独立调用也可以从其他面板切换调用。

六、最后的大总结

  1. 为了最大化组件的品质,组件的实现要讲求分工;
  2. 要想分工,需要将组件UI层东西从JS剥离出来,发挥CSS/HTML工程师的在UI处理上造诣;
  3. 想要剥离,就要求组件半封装,避开大而全的API;
  4. 所谓半封装是功能封装,UI不封装;但对于某一具体项目,组件还是完全封装的;
  5. 避开API的方法包括交给原生的HTML属性API, 以及根据项目UI场景设计API,告别无意义的生僻场景API的使用。
  6. 发挥CSS的潜力,尽量避免使用JS做一些自己为是的功能;
  7. 面向设计的组件构建思想只适用于战略级项目、或希望成为精品的项目,以及需要配备优秀设计和UI开发。否则传统的大而全的组件反而更适用。
  8. UI组件的本质是HTML和CSS, JS只是辅助,千万不要乱了主次。

最后,我们得到的就是一款UI设计精良,同时代码轻便简洁,量身定制的高级web组件,配合模块化的管理,整个项目的品质就可以有质的提升。

OK, 以上就是自己对web组件的一些思考和感悟,可能讲得有些啰嗦(概要版点这里),希望不会影响中心思想的传达。另外,大家读完之后有什么想法,或者本身就对web组件有一些思考,都欢迎留言讨论。

最后,感谢阅读!

Tags: , ,

zhang xinxu
zhang xinxu
聊出一个未来
上一遍
QQ音乐业界首创「大咖装」
下一遍
13,587
QQ空间 新浪微博 Facebook Google+

相关推荐

留下你的想法吧

  1. zhoukeke says:

    好文!非常赞同这种思想。

  2. DE says:

    我写angular 2年了, 说下我的体会
    angular 中我这样做, 把 Date 组件分为两部分, service 和 directive 。
    directive 就是 html 部分

    service 就是各种封装的强大 API 部分, 负责纯粹逻辑, 臃肿一些无所谓, 重在强大, 保持向前兼容

    service 中定制了各种丰富的生命周期、属性, 供 directive 中调用的hook。

    directive 负责使用,自定义 html 模板, 模板中可以有所选择的使用 service 中的hook

    官方组件可以有一个 directive 的默认实现, 基本上就包含基础功能, 作为参考 example。

    在个性化的项目中, 可以任性的改。 目标是使用者友好、轻量级, 容易裁切、定制。

    基本上我的 date 的service , 500行左右。 directive 几十行。 改起来很方便, 用起来不爽的话, 重写一个也要不了多久。 而且重写只要 精通 HTML / CSS 的童鞋参与, 略懂js即可。

  3. qqbunny says:

    日期选择那里,关于“选择日期的回调”,通过change事件并不能理想地解决所有需求,比如想让点某特定日期后,不往input的value中赋值,总不能等value中有值了,判断不合法再将其删除吧?这样一来,在不明确需求的情况下,到底什么叫“核心API”?如果API极少,二次开发的工作量接近从0开发,反之又陷入过度封装的限制。你认为“半封装是功能封装,UI不封装”,这大概是基于“UI复杂多变”的想法,然而事实上功能也是极容易变的。随着应用系统的发展,已经半封装起来的那部分,也可能变得臃肿,不易扩展。

  4. 并且JS调用过多,也会影响到网站的被搜索引擎的抓取等

  5. SJY says:

    日期时间组件如果使用原生的 input ,如何重绘原生的UI呢?或者能够使得原生 UI 不显示而使用自定义的也行。

  6. zhengzk says:

    哈哈~ 写的真不错嘞! 我的理解是:说到底还是要做好层级的处理,在VIEW层要提供充分的自由度,对核心功能以外的部分不做过多的限制 日期组件,是滴 网上找的组件很多时候都不满足要求,之前自己也封装过,但做的不是很好,看完这篇文章,知道该怎么做了~ 真是棒棒哒~

  7. 楼主的自我感觉有点良好了,为了正名批判没问题,但不应过度。本文举的例子大部分是经不起推敲的

    kissy为何要有统一类名前缀,需要放到kissy体系设计的交互去看,而不是看一两个组件。

    复选框的例子,该组件的开发背景,使用场景是什么?莫名其妙就拉个组件出来批斗,就好比在IE6一统江湖的年代,责怪开发者为什么总是用mouseenter而不是hover。

    跨部门合作:要嘛是楼主故意卖萌,不然就是项目管理意识、团队意识和成本意识过于薄弱了。诚如楼主提到,企业的项目交接给别人,上手维护成本高,那楼主你们那套东西让他们套,何尝不是成本。这里包括代码迁移成本、测试成本、学习成本、维护成本(这里特指后续的维护交由企业团队)。

    这是个细思极恐的问题,其他不说,新成员进来需要在两套风格迥异的组件体系间切换,直接就是效率杀手。

    时间选择器,与checkbox类似,又是脱离了具体语境的批判,不展开。

    结语:楼主抛出的这个话题挺好,原本挺有讨论价值的。可是遍观全文,举的例子实在是难以认同。对于这个主题,楼主的讨论还是太有局限性,批判甚至多余讨论。猜测是在跟企业团队的接触中有些不满情绪。可以多看看其他团队,多跟其他人聊聊。

  8. lengziyu says:

    完全封装的web组件,就好比流水线上的衣服,讲求工业化,效率以及成本。但是,这样的web组件做出来的产品的档次和质量,未免有粗糙之感。而本文所反复推崇的半封装的web组件,非常重视量体裁衣的设计、量身定制的交互,使得我们的web站点犹如私人订制的高级礼服。在芸芸产品中脱颖而出,赢得口碑。

  9. unclay says:

    思想是不错的,有待实践,最近在做公司picker,引入这个思维来尝试下

  10. KOF says:

    看着标题滚进来,读了一半读不下去了。。。觉得话题好,内容没啥用。。。

  11. yao says:

    一直也在思考类似的问题。
    真是术业有专攻,大部分前端的重构能力确实是一般的,而且现在合格的重构工程师也是少之又少。所以总会出现有一堆钩子的组件。职业素质也确实有待提高啊。

  12. jiadi0801 says:

    我们有个“数说专题”栏目,对应一个专题就需要一套UI,这类栏目的时效性很强,因此如果赶上团队FE吃紧,页面的开发也许就是整一个大背景图片,然后加一些tab导航就行了。在这么多期做过来,积累了一些比较基础的组件,这些组件在基础样式方面只赋予了一些结构方面的东西如position,float,针对每期栏目再写一套CSS;这样一个模块一个模块往上堆,根据设计稿添加一套CSS就能满足上线要求了。
    我想这种开发方式是满足你说的半封装的。
    我也抽空尝试写一些通用的组件,然而经过调研(主要是从github上搜这些组件),发现组件的需求场景变化很多,不可能在封装上做太多有用的功夫。比如如果HTML和js方面封装的好,留下CSS去定制,那么基本上组件的功能就固定了;如果需要拓展HTML,那么通常情况下CSS和js都要做一定的修改,这就导致版本差异;如果拓展JS,往后发展就是现在的成熟web组件,api太多。
    UI层的封装真心不好弄。

  13. bingo says:

    这个话题确实值得探讨,松散型和体系型的UI组件各有优缺点,楼主一味的吐槽,有失偏颇。基于松散型和体系型的UI组件的辩证关系,从另一个角度来看,是不是楼主的这套理念一样会被吐槽?进一步,按照楼主的思路,类似纯js封装的组件肯定也被吐槽,Sencha的Extjs甚至会被吐槽致死

  14. Rak says:

    抽象的颗粒度问题,为什么要改就是因为颗粒度大了,不需要的东西被迫继承。
    感觉追求的是像面向接口编程一样的做法。

  15. jinC says:

    我倒觉得楼主所说的半封装才是真正的封装和抽象。组件化应该是要HTML,CSS,JS三者各司其职的,而不是一味地交由JS来掌控。在着手开始写组件的时候,就应该对其进行思考,这应该是目前大多数前端都没有去做的事情。的确是好文,不过有些概念的定义应该再仔细琢磨一下,例如 半封装 这个。~

  16. 路人甲j says:

    分层是非常棒的思路。
    service业务和ui逻辑分层的感觉。把service接口留出去,复用别的地方的ui逻辑的函数,像是积木一样拼成一个高自定义的组件。d3.js就很有这种感觉。把数学计算交给api来做,返回的也只是一堆数字。把view交给自己来做。随便怎么利用这些处理的数据。
    但是说实话。。很多时候css能做到的还是很薄弱的。还是需要js来做计算的。
    很多ui需要的动画效果都是需要仔细思考计算的。
    比如http://www.zhihu.com/question/26382740

  17. Rocky says:

    去年写过一个小小的工具,思想跟您的有点类似:页面设计师设计好静态HTML,前端工程师往静态HTML代码中添加特殊的标签,我写的JS工具就会根据这些标签,生成组件需要的HTML片段,存储在一个全局对象中(我称之为UI树),组件需要渲染UI时,就引用这个UI树种的某个UI片段,并插入动态数据,进行渲染。这样很好地分离组件的逻辑层与UI层。页面设计只要不改动前端工程师添加的标签,爱怎么改就怎么改。

  18. 方方 says:

    我们无需包括标签就能实现我们想要的相对文本框定位效果,而且兼容IE6+

    怎么做到的,快说!

  19. 我就觉得很好,小而美。嘿嘿,大而广就这么办。