共计 4940 个字符,预计需要花费 13 分钟才能阅读完成。
本文首发于欧雷流。由于我会时不时对文章进行补充、修正和润色,为了保证所看到的是最新版本,请阅读原文。
在软件开发中,研发效率永远是开发人员不断追求的主题之一。于公司而言,在竞争激烈的互联网行业中,产出得快和慢也许就决定着公司的生死存亡;于个人而言,效率高了就可以少加班,多出时间去提升自己、发展爱好、陪伴家人,工作、生活两不误。
提升效率的途径,无外乎就是「方法」和「工具」。以一个开发者的思维来想,就是将工作内容进行总结、归纳,从一组相似的工作内容中提炼共同点,抽象出解决这一类问题的方法,从而造出便于在今后的工作中更为快速解决这类问题的工具。这个「工具」可以是个函数、组件、中间件、插件,也可以是 IDE、其他开发工具的扩展,甚至是语言。
面向组件
在现代前端开发中,如果去问一个业务前端开发:「如何提升团队开发效率?」对方所回答的内容中,极有可能会出现「组件库」。没错,在前端工程化趋近完善的今天,在近几年 React、Vue 等组件化库 / 框架的影响下,面向组件开发的思维方式早已深入人心。
组件库提效有限
现在,组件库已经是一个前端团队的必备设施了,长远来看,团队一定且必须要有自己的组件库。开源的第三方组件库再好,对于一家企业的前端团队来说也只是短期用来充饥的,因为它们无法完全满足一家公司的业务场景,并且出于多终端支持的考虑,必定要进行二次开发或者自研。
组件库有了,团队和公司中推广的效果也不错,绝大多数的人都在用。使用组件开发页面相对 jQuery 时代要每块功能区都得从 <span>
、<div>
等 HTML 标签码起来说确实提升了效率,然而有限;要搞出页面需要反复去引入组件,然后组合拼装出来,就像工厂流水线上的工人拼装零件,仍然要去做很多重复动作。
只要觉得当前的开发方式重复的动作多了,就代表还能继续提效,得想个法子减少重复无意义动作。
面向组件的开发方式,是现代前端页面开发提效的初级阶段,也是一个团队所要必经的阶段。
更高层面的提效
在之前写的文章中有段话——
组件可以很简单,也可以很复杂。按照复杂程度从小到大排的话,可以分为几类:
- 基础组件;
- 复合组件;
- 页面;
- 应用。
对,不用揉眼睛,你没有看错!
站在更高的角度去看,「页面」和「应用」也是一种「组件」,只不过它们更为复杂。在这里我想要说的不是它们,而是「基础组件」和「复合组件」。
——欧雷《我来聊聊面向组件的前端开发》
文中提到了「页面」和「应用」也可以看作是种「组件」。虽然与当时的想法有些差异,但本文的内容就是要在那篇文章的基础上简单聊聊在「页面」层面的提效。
一般来说,「页面」是用户所能看到的最大、最完整的界面,如果能在这个层面有个很好的抽象方案,在做业务开发时与单纯地面向组件开发相比,应该会有更大的提效效果。
GUI 发展了几十年,人机交互的图形元素及布局方式已经相对固定,只要不是出现像 Google Glass 之类的革命性交互设备,就不会发生重大改变。在业务开发中界面形式更是千篇一律,尤其是 web 页面,尤其是中后台系统的 web 页面,一定可以通过什么方式来将这种「千篇一律」进行抽象。
试着来回想下,自己所做过的中后台系统的绝大部分页面是不是我所描述的这样——
页面整体是上下或左右布局。如果是上下布局的话,上面是页头,下面的左侧可能有带页面导航的侧边栏,或者没有侧边栏直接将页面导航全部集中在页头中,剩余区域是页面主体部分,承载着这个页面的主要数据和功能;如果是左右布局,左侧毋庸置疑就是有页面导航的侧边栏,页头跑到了右侧上面,其余是页面主体。
中后台系统的主要功能就是 CRUD,即业务数据的增删改查,相对应的页面展现及交互形式就是列表页、表单页和详情页。列表页汇总了所有业务数据的简要信息,并提供了数据的增、删、改和更多信息查看的入口;表单页肩负着数据新增和修改的功能;详情页能够看到一条业务数据记录最完整的信息。
每新增一个业务模块,就要又写一遍列表页、表单页和详情页……反复做这种事情有啥意思呢?既然这三种页面会反复出现,那干脆封装几个页面级别的组件好了,有新需求的时候就建几个页面入口文件,里面分别引入相应的页面组件,传入一些 props
,完活儿!
这种方式看起来不错,然而存在几个问题:
- 没有描述出页面内容的结构,已封装好的页面组件对于使用者来说算是个黑盒子,页面内容是什么结构不去看源码不得而知;
- 如果新需求中虽然需要列表页、表单页和详情页,但与已封装好的能够覆盖大部分场景的相关组件所支持的页面有些差异,扩展性是个问题;
- 每来新需求就要新建页面入口文件然后在里面引入页面组件,还是会有很多无意义重复动作和重复代码,时间长了还是觉得烦。
我需要一种既能看一眼就理解内容结构和关系,又具备较好扩展性,还能减少重复代码和无意义动作的方式——是的,兜了一个大圈子终于要进入正题了——面向模板开发。
面向模板
面向模板的前端开发有三大要素:模板;节点;控件。
富有表达力的模板
我所说的「模板」的主要作用是内容结构的描述以及页面的配置,观感上与 XHTML 相近。它主要具备以下几个特征:
- 字符全部小写,多单词用连接符「-」连接,无子孙的标签直接闭合;
- 包含极少的具备抽象语义的标签的标签集;
- 以特定标签的特定属性的形式支持有限的轻逻辑。
为什么不选择用 JSON 或 JSX 来描述和配置页面?因为模板更符合直觉,更易读,并且中立。用模板的话,一眼就能几乎不用思考得看出都有啥,以及层级关系;如果是 JSON 或 JSX,还得在脑中进行转换,增加心智负担,并且拼写起来相对复杂。Vue 上手如此「简单」的原因之一,就是它「符合直觉」的设计。
要使用模板去描述页面的话,就得自定义一套具有抽象语义的标签集。
页面的整体布局可以用如下模板结构去描述:
<layout>
<header>
<title> 欧雷流 </title>
<navs />
</header>
<layout>
<sidebar>
<navs />
</sidebar>
<content>...</content>
</layout>
<footer>...</footer>
</layout>
看起来是不是跟 HTML 标签很像?但它们并不是 HTML 标签,也不会进行渲染,只是用来描述页面的一段文本。
整体布局可以描述了,但承载整个页面的主要数据和功能的主体部分该如何去描述呢?
在上文中提到,我们习惯将中后台系统中与数据的增删改查相对应的页面称为「列表页」、「表单页」和「详情页」。虽然它们中都带有「页」,但真正有区别的只是整个页面中的一部分区域,通常是页面主体部分。它们可以被分别看成是一种视图形式,所以可以将称呼稍微改变一下——「列表视图」、「表单视图」和「详情视图」。一般情况下,表单视图和详情视图长得基本一样,就是一个能编辑一个不能,可以将它们合称为「表单 / 详情视图」。
「视图」只描述了一个数据的集合该展示成啥样,并没有也没法去描述每个数据是什么以及长啥样,需要一个更小粒度的且能够去描述每个数据单元的概念——「字段」。这样一来,用来描述数据的概念和模板标签已经齐活儿了:
<view>
<field name="name" label="姓名" />
<field name="gender" label="性别" />
<field name="age" label="年龄" />
<field name="birthday" label="生日" />
</view>
虽然数据能够描述了,但还有些欠缺:表单 / 详情视图中想将字段分组展示没法描述;对数据的操作也没有描述。为了解决这两个问题,再引入「分组」和「动作」。这下,表单 / 详情视图的模板看起来会是这样:
<view>
<group title="基本信息">
<field name="name" label="姓名" />
<field name="gender" label="性别" />
<field name="age" label="年龄" />
<field name="birthday" label="生日" />
</group>
<group title="宠物">
<field name="dogs" label="????" />
<field name="cats" label="????" />
</group>
<action ref="submit" text="提交" />
<action ref="reset" text="重置" />
<action ref="cancel" text="取消" />
</view>
模板很好地解决了内容结构描述和配置的问题,但如何去动态地调整结构和更改配置呢?在平常的业务页面开发时也许不会太凸显出问题,但碰到流程表单设计或页面可视化编辑这种灵活性很高的需求时,问题就会被暴露出来了。
充满控制力的节点
在这里,我要将定义好的标签集所拼成的模板解析成节点树,通过更改树的结构和节点的属性去影响页面最终的呈现效果。每个节点都会有节点的基本信息、对应标签的属性和一些节点操作方法:
{
name: "field",
tag: "field",
attrs: {
name: "name",
label: "姓名"
},
parent: {},
children: [],
remove: function() {},
insert: function() {}
}
在页面模板化且节点化之后,理想情况下,页面长啥样已经不受如 React、Vue 等运行时技术栈的束缚,控制权完全在解析模板所生成的节点树上,要想改变页面的视觉效果时只需更改节点即可。
极具表现力的控件
页面内容的描述通过模板来表达了,页面内容的控制权集中到节点树中了,那么页面内容的呈现在这种体系下应该如何去做呢?负责这块的,就是接下来要说的面向模板开发的第三大要素——控件。
「控件」这个词不新鲜,但在我所说的这个面向模板开发的体系中的含义,需要被重新定义一下:「控件」是一个可复用的,显示的信息排列可由用户改变的,可以进行交互的 GUI 元素。
在这个面向模板开发的体系中,模板和节点树完全是中立的,即不受运行时的技术栈所影响;而控件是建立在运行时技术栈的基础之上,但不必限于同一个技术栈。也就是说,可以使用 React 组件,也可以用 Vue 组件。
每个控件在使用前都需要注册,然后在模板中通过 widget
属性引用:
<view widget="form">
<group title="基本信息" widget="fieldset">
<field name="name" label="姓名" widget="input" />
<field name="gender" label="性别" widget="radio" />
<field name="age" label="年龄" widget="number" />
<field name="birthday" label="生日" widget="date-picker" />
</group>
<group title="宠物" widget="fieldset">
<field name="dogs" label="????" widget="select" />
<field name="cats" label="????" widget="select" />
</group>
<action ref="submit" text="提交" widget="button" />
<action ref="reset" text="重置" widget="button" />
<action ref="cancel" text="取消" widget="button" />
</view>
这样,一个面向模板开发的普通表单页出来了!
思想总结
面向模板的开发方式很好,能够大幅度提高业务前端开发效率,一定程度上减少了业务系统的搭建速度;作为核心的模板和节点树是保持中立的,大大降低了运行时技术栈的迁移成本,且能够应对多端等场景。
面向模板的开发方式初期投入成本很高,标签集、模板解析和控件注册与调用机制等的设计和实现需要较多时间,并且这仅仅是视图层,逻辑层也需要做出相应的变化,不能简单地用 props
和事件绑定进行处理了。
这个体系建成之后,在业务开发上会很简单,但机制理解上会增加部分开发人员的心智负担。
为了效率,一家公司里的业务前端开发到最后一定是面向模板,而非面向组件。