背景
上周发了一篇文章 2023年的前端工程化治理之路 - 掘金 (juejin.cn),限于时间和篇幅,写的比较简略。 因此,新写一篇,进行完善和细节的补充。
实践
1. 发布
这部分的内容,前文中已经写的差不多了,具体的细节网上也能找到很多教程,因此不再赘述。
注意
底层工具的迁移,涉及的范围非常广,因此上线的风险是非常高的。一定要协调好产品,测试,确定上线的时间,提前做好回滚方案和测试方案。
2. 沉淀可复用物料
2.1高频组件沉淀
高频组件类型
对于B端业务来说,高频的组件主要是几个
- Table,主要的场景是展示和编辑
- Form,主要的场景是CRUD的搜索和编辑
- Page,主要的场景是新增/编辑/详情页面的布局
- Description,主要的场景是详情
做组件主要分两个方向,第一是做交互和功能上的增强,第二是满足业务上定制化的需求。
对于交互和业务上的内容,我们本文不做赘述,主要讲组设计上的考量。
可配置化
我们以一个典型的CRUD页面来说,一个CRUD主要包含了几个部分:
- 顶部的搜索栏
- 底部的表格
- 点击查看时跳转到详情页或者详情弹窗
- 点击编辑时跳转到编辑页或者编辑弹窗
我们会发现,这几个组件有非常多的相同字段,比如对于一个查询用户的CRUD来说,在这4个部分可能都要去展示"名字"这个字段。
在antd中,我们需要这样实现:
- 搜索栏
- Table
- Description
- 同1
我们需要对一个字段进行三次配置。这显然过于麻烦。
一方面,我们让组件支持了可配置化,通过配置Items
数组参数,即可完成对页面字段的映射。对于表单常用的组件,比如筛选,文本输入,数组输入,时间选择等,我们做内置的支持,用户仅需配置字段类型即可。
另一方面,我们将这三个组件的字段配置进行统一,抹平差异:
这样的话,我们仅需一个配置数组,再根据场景进行过滤和自定义render即可完成一个CRUD的配置:
配置化的程度问题
在可配置化的路径上,一个我认为的邪路,就是做成watch:['name']
这样的方式。我们在实现的时候,也参考了Xrender这样的开源方案,经过使用,我们还是摒弃了这种大量schema的方案。
我认为schema存在以下问题:
- 容易写很长很复杂,难以维护
- schema 无法利用TS类型提示,一旦出问题难以排查
- schem处理联动的方式不灵活
- 上手难度高,且投入产出比较低
因此,我们没有采用全量配置化的方式,而是使用了render函数,让用户处理自定义和联动的场景。
为了增强组件的可复用程度,我们协调了多团队的UI和产品进行设计,对组件进行文档和demo的完善,这样组件建设之后可以被更多人使用,扩大成果。
2.2 VScode插件助手
自动化配置
在日常开发中,页面显示什么,往往跟数据结构是相关的。如果我们知道了数据结构,那么就可以根据数据结果,生成一个配置化的数组。
由于我们的业务复杂,接口返回的数据结构也较为复杂,手动建模也是很耗时的事情。
因此,我们做了以下功能:
- 根据YAPI的接口返回的JSON,生成TS类型
- 用户在代码中选择TS类型或者接口时,可以调用插件生成常用组件的配置,如Form,Table等
有了以上的功能,我们自然而然的想到,如果我们内置一些模板,动态生产它的配置岂不是效果更好?
模板带来的好处
提效
因此,我们将一些典型场景,如CRUD,可编辑的Table,多From的Page等场景,写成插件内置的模板。当用户生成配置之后,将这些配置动态写入到模板中,同时生成mock数据。这样,用户只需根据后端YAPI的文档,就能秒生成一个可以运行,可以调试的页面。
这样的效果是提效的一环,同时也是代码质量的一环。
灵活
我们也避免了把组件做的过大。
比如还是CRUD来讲,通过模板,我们就可以把搜索栏和表格,分页逻辑,页面状态等放入模板中,交给用户管理。而如果不用模板,那很可能需要一个将其结合起来的叫ProCurd
的组件了,组件自己来维护状态和一些搜索栏重置,提交的逻辑。这样的话,灵活性就欠缺了。
标准实践
业务开发时,时常会遇到这样的页面,单一页面内,放置了很多个可编辑的Form。
这种场景下,由于提交前校验的存在,很多开发都不得不用一个Form将所有card里的form包起来。而我认为这种方式会把很多相对独立的逻辑糅合到一起。因此,在组件开发时,我们就给这些组件
提供了非常多的ref方法,既方便用户在逻辑上进行拆分,也方便用户在必要时进行数据之间的交互和联动。如这样一个复杂的页面,我们的index文件,非常的简洁:
各个Card的业务逻辑,在CustomForm中单独实现即可。
2.3 研发流程提效
- 前端的流程和规范
这主要包含了一些代码规范,命名规范,技术设计等规范。
流程则包含,组件库升级,代码发布,回滚,缺陷复盘等。
- 后端的规范
这主要包含前后端的接口规范。我们对于接口的方法,返回的错误码定义,数据结构定义,通用字段定义,字段类型定义,来提升我们的接口质量,降低前后端因为接口改动来回沟通的成本。
- 产品的规范
这主要包含交互规范,需求准入规范,需要排期规范等约定
- QA的规范
这主要包含用例评审规范,转测规范,定期的缺陷复盘等
2.4 长期的治理
静态代码分析
为了防止代码劣化,我们开发了一个静态分析工具。一方面,可以分析出无效的代码文件,在仓库中进行清理。另一方面,可以根据变动的文件,向上搜寻,查出可能影响到的前端路由页面,便于进行回测。
其实现原理如下:
这里面的技术点主要是两个
- 路由算法分析
我们采用的ReactRoute来进行的配置的,写法相对固定,我们可以根据关键字进行分析
a. 首先遍历.router文件夹,读取代码。并解析出包含有path和component的对象,并找出component对应的导入。(这里如果要解决上述几种变量重命名的问题,就要使用到babel的scope,这个就复杂了)
b. 读取ModuleLoader中的导出源码,并匹配关键字AsyncModuleLoader,找到组件对用的文件路径。
由于从代码中读取的文件路面,在匹配路径的过程中,需要根据项目打包工具的alias,extensions做同等的配置。这样才能准确的将相对路径转换为绝对路径。
- 导入导出分析
引用组件,一般有四种写法
根据使用的频率,我们支持了第一种和第三种分析。对于依赖图的分析并不复杂,我们根据路由的入口文件,使用深度遍历,进行递归即可。
领域建模
DDD的开发方式,在后端较为流行,包含的内容也较多。考虑到我们业务的复杂性,我们在前端也借鉴了DDD的一部分思想,就是与后端一样进行领域建模。
比如对于一个常见的供应链系统来说:
我们把采购的整个链路,叫做采购域。在这个域里,相同概念的东西保持一致。
一瓶饮料,在商品域和在采购域是不同的模型。
比如在商品域,这瓶饮料要包含名字,条码,sku。
而在采购域,这瓶饮料就仅仅需要Sku和名字。
这样的好处是,每个域的数据是精准的,易于理解的,认知成本也会降低。
同时我们将前端数据分为了两层:
这样的好处是,用户逻辑和业务逻辑进行解耦。比如用户在页面操作的是一个字符串,但是提交到后端时,需要将其分割成一个数组,那么这个逻辑就可以放在Modal层去做。
但是数据的来回转换是非常累人的,因此我们采用了AOP的方式来实现转换。
总结
以上就是主要的细节,希望我们的经验对大家有所帮助。