聊聊前端 UI 组件:组件特征

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文首发于"},{"type":"link","attrs":{"href":"https://ourai.ws","title":null},"content":[{"type":"text","text":"欧雷流"}]},{"type":"text","text":"。由于我会时不时对文章进行补充、修正和润色,为了保证所看到的是最新版本,请阅读"},{"type":"link","attrs":{"href":"https://ourai.ws/posts/the-features-of-frontend-ui-components/","title":""},"content":[{"type":"text","text":"原文"}]},{"type":"text","text":"。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文是文章系列「"},{"type":"link","attrs":{"href":"https://ourai.ws/series/talking-about-frontend-ui-components/","title":""},"content":[{"type":"text","text":"聊聊前端 UI 组件"}]},{"type":"text","text":"」的第二篇,内容与本系列的上篇文章《"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/63788ce9260cdbc214523651f","title":""},"content":[{"type":"text","text":"聊聊前端 UI 组件:核心概念"}]},{"type":"text","text":"》有所关联,如果还没看过,建议去看下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的主要内容是根据特征对前端 UI 组件进行建模,让我们尽可能充分地了解它的方方面面,并为如何设计以及建立一个组件体系打下基础。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"组件构成"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从关注点分离的角度分解 UI 组件,并分析其各部分的易变性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"构成元素"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个完整的具备功能的 UI 组件的构成,有结构(structure)、表现(presentation)和行为(behavior)这三个方面。为什么强调是「完整的具备功能的 UI 组件」?是因为它是一个最全的特征集合,而其他的「UI 组件」可能会缺少一些特征,从而使分析不那么完善。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到「结构」、「表现」与「行为」这三个词,作为一名有经验的前端开发者,很自然地就会想到很久很久之前开始一直提倡的前端开发的关注点分离——HTML 负责组织页面结构,CSS 负责网页内容的表现样式,JS 则负责用户与网页之间的交互,它们各自扮演着不同且相辅相成的角色。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/22/22826761298495c9ab6020cf7c5d3377.png","alt":null,"title":"关注点分离","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而在这里,它们的含义会有所不同——"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"经 HTML 组织后的网页内容是「结构」没错,但它仅仅是代码层面的,未被 CSS 所影响的结构。最终的视觉呈现,也就是视觉层面的「结构」,应该还包括 CSS 的布局类样式,如定位(positioning)、浮动(float)等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CSS 中的那些非布局类样式,如颜色、字体、文本、边框、尺寸、留白等类别的样式,以及图标、图片,皆为「表现」。这些一般还被称为「主题风格」或者「皮肤」。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可在 JS 中运行的事件系统以及进行逻辑处理的函数和对象方法,算是「行为」——这就是 UI 组件的交互逻辑了。当然了,与交互逻辑相融合的业务逻辑,也是「行为」的一部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"易变性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据构成 UI 组件的每个部分的性质,会影响 UI 组件相应部分的易变性——对于组件复用来说,该部分是相对稳定的还是相对不稳定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GUI 发展了几十年,人机交互的图形元素及布局方式已经相对固定,只要不是出现像 Google Glass 之类的革命性交互设备,就不会发生重大改变。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"——欧雷《"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/9814af3887ef2f2e20c755ee4","title":""},"content":[{"type":"text","text":"我来聊聊面向模板的前端开发"}],"marks":[{"type":"italic"}]},{"type":"text","marks":[{"type":"italic"}],"text":"》"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上所述,未来到底会出现什么样的变革性交互方式无从得知,但我认为,只要是需要用眼去看且用手去操作,交互方式就逃不离这几十年来未有啥变革的形式。因此,UI 组件的视觉结构和交互逻辑是最不易变的,且无论是交互模式还是触发事件都是可枚举的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果单纯从最终的 HTML 结构上来看,它也算是不易变的,但在现代前端开发中,HTML 的结构基本是动态生成的,并且强依赖于 React、Vue 这类没有统一标准的 JS 库/框架。另外,还存在着像 "},{"type":"link","attrs":{"href":"https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/","title":null},"content":[{"type":"text","text":"WXML"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https://opendocs.alipay.com/mini/framework/axml","title":null},"content":[{"type":"text","text":"AXML"}]},{"type":"text","text":" 这类平台限定的视图结构描述语言。由于写法不一致,这就使页面内容结构变得不那么稳定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般来说,离用户越近的东西越容易发生改变。在网站、应用中离用户最近的是 GUI,而在 GUI 中离用户最近的则是主题风格,这是对用户来说最直观的东西。主题风格会随着 GUI 设计人员(通常是设计师)的审美和想法而改变,所以它是最易变的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为业务逻辑是进行业务相关处理的核心所在,如果业务规则变了,相应的代码实现也得跟着改变,所以业务逻辑也是很易变的。业务逻辑对于一个网站、应用来说是十分必要且重要的,但对 UI 组件来说,它就没那么必要了,更谈不上重要。在前端的 GUI 层面,业务逻辑理应是交互逻辑的延伸。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了方便,将 UI 组件各个构成部分的易变性及其影响因素整理如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/43/43a5c5c20208679509092a163bde4db1.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从表格中可以看出,将「易变性」分成了三个等级,按照从小到大的顺序来分别解释下:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"「不易变」——受交互方式影响,只要没发生什么革命性改变时就不太会变;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"「较易变」——受源码语法影响,只要源码的编写方式确定就不会变;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"「很易变」——受业务和人影响,只要业务领域、业务规则还有人的想法不变就不会变。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"组件分类"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以较为抽象的视角对 UI 组件进行分类——"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"原子性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从一个 UI 组件的内部是否还包含其他 UI 组件这个角度来看,分为「原子组件」和「复合组件」两类。「原子组件」是不可再分的 UI 组件,即其内部不再包含其他的 UI 组件,像按钮组件、图标组件、分割线组件等;「复合组件」则是由一个以上的原子组件所构成的,如导航菜单组件、选项卡组件、对话框组件等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"通用性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据 UI 组件的通用性,可分为「通用组件」和「专用组件」。「通用组件」是能够满足大部分常规场景的 UI 组件,它们的集合通常会作为「组件库」整体打包发布为一个软件包;「专用组件」是为了解决某些特殊场景需求而存在的,像数据网格、各种编辑器等,这类一般都是单独发包。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"功能性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照 UI 组件所起到的作用,大概可划分为以下几类:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a9/a94e216bc887866fded353ebf4717889.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种分类方式没有一个严格的定义,就见仁见智了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"组件继承"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不像面向对象编程中的继承那样是行为的复用,这里所说的「继承」是指表现的复用,并且还能够「多重继承」。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在继续往下之前,先引入一个「虚拟组件」的概念。正如它的名字所示,是一个虚拟的,实际不存在的,只是概念上的组件。它是几个主题风格属性的集合。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"输入框组件、下拉列表组件等都属于"},{"type":"link","attrs":{"href":"https://html.spec.whatwg.org/#forms","title":null},"content":[{"type":"text","text":"表单控件"}]},{"type":"text","text":"(form control),它们都继承自「表单控件」这个虚拟组件,如果各自没有指定颜色、字体、边框等主题风格属性的话,将会按照虚拟组件中所设定的来显示。类似地,下拉列表组件、下拉菜单组件等都有弹出层(pop-up),它们都继承了「弹出层」这个虚拟组件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想必你已经发现了,下拉列表组件同时继承了「表单控件」和「弹出层」这两个虚拟组件,这就是上面提到的「多重继承」。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那些所谓的「虚拟组件」,它们也遵循着同样的继承规则——如果自身没有指定特定的主题风格属性,则会按照父级所设定的显示。那么,虚拟组件的「父级」是啥呢?——是基础风格。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然这里所描述的继承关系看起来有点像 CSS 中的"},{"type":"link","attrs":{"href":"https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance","title":null},"content":[{"type":"text","text":"继承"}]},{"type":"text","text":",然而它们并不一样。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文从前端 UI 组件的构成、分类及组件间的继承关系等角度出发,通过分析组件的特征来探讨建立一个组件体系所需要关注的一些点。其中,UI 组件各构成元素的易变性是最应该被关注的,它会对组件体系的可复用性、可扩展性等产生很大影响。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实质上,HTML 与 WXML 和 AXML 等是同一级别的,都是平台限定的视图结构描述语言——WXML 是微信小程序的,AXML 是支付宝小程序的,而 HTML 则是「网页浏览器」这个平台的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在动态网页中,尤其是使用了 "},{"type":"link","attrs":{"href":"https://mustache.github.io/","title":null},"content":[{"type":"text","text":"Mustache"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https://handlebarsjs.com/","title":null},"content":[{"type":"text","text":"Handlebars"}]},{"type":"text","text":" 等模板引擎或 React、Vue 这类库/框架时,最终的内容结构基本是 JS 生成的,这就强化了 JS 而弱化了 HTML 在内容结构上的控制力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"各个 JS 库/框架的 HTML 代码生成方式不同,视图结构描述语法不同,没有统一的标准,这就造成了混乱——这也是对前端 UI 组件的可复用性最大的挑战!"}]},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"欢迎关注微信公众号以及时阅读最新的技术文章"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/15/15ca057e8c81c613f5a34d41b84b7125.jpeg","alt":null,"title":"Coding as Hobby","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章