一、前言
在前端领域基于物料开发已经成为业界共识,有很多优秀的物料,如antd、ahoooks等,这些为我们在开发过程中提供了很大的帮助。但由于各个团队业务不同、技术生态不同,所以很多团队在此基础上开发了更贴合自己需求的物料库。作者负责过物料领域的建设,本文就从实际需求出发谈一下物料的划分、需要什么样的物料、怎么建物料的问题。篇幅有限,讲的不清楚的地方欢迎私下交流。
二、定义
什么是物料?
在前端领域中,物料(Material)是一个重要且广泛使用的概念。它指的是组成一个前端项目的不同单位,这些单位可以根据规范进行标准化构建,并且可以在不同项目、不同团队、不同成员之间复用。物料体系是前端开发中重要的生产力要素,极大地提高了开发效率和代码的可维护性。
物料的分类?
很多同学把物料等同于组件库,事实上根据上面「物料」的定义,组件库只是其中一种物料,只要是能够复用的并且可以进行标准化构建的都可以是物料的一种。我经常性的把物料分为以下几种。
原子组件(Basic Component)
原子组件位于物料层级的最基础单元,它们是构成用户界面的基本元素,如按钮(Button)、输入框(Input)等。这些组件的设计遵循最小化原则,专注于单一职责,强调高度的通用性和可复用性。
业务组件(Business Component)
业务组件是在原子组件基础上的进一步抽象与封装,它针对特定业务场景进行了功能增强或组合,以更好地适应项目需求。这类组件贴合了团队的业务技术特征。例如,一个输入框可以为他定制业务逻辑的校验。
区块(Block)
区块是更高层次的组织形式,由业务组件通过复杂的逻辑和布局组合而成,形成了具有高度内聚力的功能模块。它们像是软件架构中的“乐高积木”,允许开发人员快速搭建页面结构,同时可以保留了一定程度的定制灵活性。区块的引入,不仅加速了开发速度,还促进了代码的模块化管理,便于维护与复用。他跟组件的区别是组件不能更改只能引用,而区块可以引用可以修改代码进行灵活定制。
页面模板(Page Scaffold)
页面模板则是区块的集成,代表了用户界面的一个完整部分或是一个完整的页面框架。它将多个区块按照特定的布局和交互逻辑组织起来,形成可以直接面向用户的界面。页面模板的设计注重用户体验的整体连贯性,减少了从零开始构建页面所需的时间和工作量,使得开发者能够更快地聚焦于业务逻辑的实现而非界面布局。
函数(Function)
函数是从项目中提取出的通用功能代码段形成的可以重复调用的方法。如ahooks、lodash这样的函数库以及团队内部开发的Utils库也是此类物料的实例。函数显著提高了代码的简洁性和执行效率,优化了代码结构,促进了技术的沉淀。
综上所述,物料的分类并不简单是一个组件库,它覆盖了从界面元素到逻辑处理,再到页面结构所有可以提炼出的重复元素,是开发中实现高效、高质量编码的核心策略之一。通过对这些物料的合理规划与运用,不仅能提高研发效率,还能在保证代码质量的同时,提升团队的协作效率和创新能力。
三、思考
解决什么问题?
研发和设计的协作?
在所有的研发角色中,设计师是前端的上游,设计团队为用户体验负责,承担设计标准和交互规范的制定,研发负责技术上的实现,为产品功能的交付负责。也就意味着我们做的物料库是要遵循设计师的设计规范,尽管我们可以提出很多意见但不影响这个事实。所以物料离不开设计师的参与。故物料的研发应该是如下的流程。
首先antd作为一个基础的组件库,团队的基础设计规范和基础开发规范要以antd的风格为基础,在此基础上做符合团队业务特点的定制,然后再根据业务发展设计出物料的原型和交互,研发根据设计团队标准做实现,最后沉淀出物料。
怎么避免重复和无效?
为了避免重复造轮子和出现无效的组件而浪费开发资源,需要制定一套流程来保证产出的组件是实际需要的。
四、物料的标准化
组件库设计原则
在前端领域,组件库设计原则是多方面的,旨在确保组件的标准化、可用性、可复用性、一致性、可维护性和可扩展性。以下是一些关键的组件库设计原则:
标准化
统一的版本规范、命名规范、属性规范、事件规范、接口规范、交互规范、状态管理规范等。使得开发和使用上的体验保持一致。
易用性
- 容易上手:确保符合标准化规范,设计规范简洁明了的API、属性,减少不必要的配置项,使组件易于使用和理解。
- 用户友好:站在用户的角度上,考虑最终用户的使用习惯,确保组件的操作逻辑直观易懂。
一致性
- 视觉样式一致:定义统一的设计变量,如颜色、字体、间距等,确保组件在视觉上的一致性。
- 交互模式一致:组件间的交互行为应当保持一致,以提升用户体验。
单一职责
- react的哲学之一就是单一职责,根据单一功能原则来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件。
可扩展性与可定制性
- 可扩展性:组件设计应考虑到未来的需求变化,提供扩展点或接口,以便在不修改组件核心逻辑的情况下增加新功能。
- 可定制性:提供足够的配置项或插槽,允许开发者根据具体需求定制组件的样式或行为。
性能优化
- 轻量级:尽量减少组件的体积和复杂度,提高加载速度和渲染性能。
- 按需加载:使用按需加载技术,减少初始加载时间。
版本管理与兼容性
- 版本管理:按照版本规则合理控制版本的发布,做好版本之间的衔接,避免造成因版本混乱导致的问题。
- 向后兼容性:尽量保持组件库的向后兼容性,避免因为版本更新而导致现有应用出现问题。
安全生产
- 可灰度:通过灰度的验证来保证正式发布的稳定性。
- 可监控:确保出现问题能够第一时间发现。
- 可应急:确保出问题能第一时间给用户应急方案。
综上所述,前端组件库的设计原则是一个综合性的考虑,旨在通过合理的设计和实现,提升组件的可用性、可复用性、一致性、可维护性、可扩展性和可访问性,从而满足前端开发的多样化需求。
物料开发标准
版本规范
NPM 包的版本命名遵循语义化版本控制(Semantic Versioning)。版本号遵循主版本号.次版本号.修订号的格式,其中每个数字的更新规则如下:
- 主版本号(major):当你做了不兼容的API更改时更新。
- 次版本号(minor):当你添加向后兼容的功能时更新。
- 修订号(patch):当你修复了向后兼容的bug时更新。
举例:
1.0.0:首个稳定版本。1.0.1:修复了bug。1.1.0:添加了新功能。2.0.0:进行了不兼容的API更改。
命名规范
总原则遵循antd(Ant Design)中,组件的命名、组件属性的命名、组件方法的命名以及组件事件的命名。
1. 组件的命名规范
语法结构:PascalCase(大驼峰命名法)
解释和说明:
- 组件名称使用PascalCase命名法,即每个单词的首字母都大写,单词之间不使用分隔符。这种命名方式在英语中常用于类名、接口名等,有助于在代码库中快速识别组件。
- 示例:
Button、Table、DatePicker等。这些名称直接反映了组件的用途或类型,易于理解和记忆。
2. 组件属性的命名规范
语法结构:camelCase(小驼峰命名法)
解释和说明:
- 组件属性使用camelCase命名法,即第一个单词首字母小写,后续单词首字母大写。这种命名方式在英语中常用于变量名、函数名等,使得属性名称更加简洁明了。
- 示例:
size、disabled、defaultValue等。这些属性名称直接描述了组件的某个状态或配置选项。
3. 组件方法的命名规范
语法结构:camelCase(小驼峰命名法),根据功能或用途进行命名
解释和说明:
- 组件方法同样使用camelCase命名法,但更重要的是方法名称应该能够准确反映其功能或用途。虽然方法名称不一定以特定前缀开头(如
render),但通常会遵循一定的命名模式。 - 示例:
renderHeader(渲染表头的方法)、handleClick(处理点击事件的方法)、validateInput(验证输入的方法)等。这些名称中的动词(如render、handle、validate)和名词(如Header、Click、Input)结合,清晰地描述了方法的行为和目标。
4. 组件事件的命名规范
语法结构:camelCase(小驼峰命名法),通常以on开头
解释和说明:
- 组件事件处理函数的命名遵循camelCase命名法,并且通常以
on开头。这种命名方式在英语中常用于表示“当...发生时”的含义,与事件处理函数的用途相吻合。 - 示例:
onChange、onClick、onScroll等。这些名称中的on前缀和后续的动词(如Change、Click、Scroll)结合,清晰地表示了当特定事件发生时应该执行的操作。
五、物料的开发
目录结构
qt-components
├─ site // 官网静态站点
├─ dist // umd 格式产物
│ ├─ index.min.css
│ ├─ index.css
│ ├─ index.min.js
│ └─ index.js
├─ es // esm 格式产物
│ ├─ EasyModal
│ │ ├─ index.css
│ │ ├─ index.d.ts
│ │ ├─ index.js
│ │ ├─ index.js.map
│ │ └─ index.less
│ ├─ index.d.ts
│ ├─ index.js
│ └─ index.js.map
├─ f2elint.config.js
├─ lib // cjs 格式产物
│ ├─ EasyModal
│ │ ├─ index.css
│ │ ├─ index.d.ts
│ │ ├─ index.js
│ │ ├─ index.js.map
│ │ └─ index.less
│ ├─ index.d.ts
│ ├─ index.js
│ └─ index.js.map
├─ src // 组件主要逻辑目录
│ ├─ Button // 组件
│ │ ├─ __tests__ // 测试相关
│ │ ├─ _example // demo实现
│ │ ├─ readme.md // 说明文件
│ │ ├─ index.css // css样式
│ │ ├─ index.less // less样式
│ │ └─ index.tsx // 组件主逻辑
│ ├─ index.tsx // 组件库主逻辑
├─ test // 测试相关
├─ stories // storybook文档库相关
├─ README.md
├─ package-lock.json
├─ package.json
└─ tsconfig.json
技术选型
Ts VS Js
- TS
Ts是Js的超集,Ts在类型种类、类型推导、类型检测、面向对象语言特性、接口抽象等方面的优势,使得Ts在开发效率、代码规范、开发质量、开发体验上都更好。
Function Components VS Class Components
- Function Components
| 特征 | Hooks (函数组件 + Hooks) | Class Components |
|---|---|---|
| 语法简洁性 | 更简洁,不需要复杂的类和构造函数 | 相对复杂,需要定义类和构造函数 |
| 状态管理 | 使用 useState 管理状态,更方便 | 使用 this.state 和生命周期方法管理状态,比较复杂 |
| 副作用处理 | 使用 useEffect 处理副作用,如数据获取、订阅/取消订阅 | 使用 componentDidMount, componentDidUpdate, componentWillUnmount |
| 条件渲染 | 使用标准的 JavaScript 条件语句 | 同上,使用标准的 JavaScript 条件语句 |
| 性能优化 | 使用 useMemo 和 useCallback 优化性能 | 使用 shouldComponentUpdate 或 PureComponent |
| 可复用性 | Hooks 更易于复用和组合,可将状态逻辑提取为独立的Hook | 状态逻辑和UI逻辑混杂,复用性较差 |
| 学习曲线 | 对于熟悉函数式编程的开发者,学习曲线较平缓 | 对于面向对象编程背景的开发者,可能更自然 |
| 错误处理 | 使用 try...catch 结构捕获异步错误 | 使用 ErrorBoundary 组件捕获错误 |
| this使用 | 不使用this | 使用复杂 |
React团队已经明确表示函数组件和Hooks是未来的方向,因为它们提供了更现代、更简洁、更易于理解和调试的代码。
受控组件 VS 非受控组件
- 受控组件
非受控组件:由dom自己维护和控制组件的数据处理和展示。
受控组件:由程序通过state和onChange来维护和控制组件的数据处理和展示。
| 特性 | 非受控 | 受控 |
|---|---|---|
| 不变的值 | ✅ | ✅ |
| 提交时验证 | ✅ | ✅ |
| 实时验证 | ❌ | ✅ |
| 有条件动态展示 | ❌ | ✅ |
| 强制输入格式 | ❌ | ✅ |
| 多种格式输入 | ❌ | ✅ |
| 动态的值 | ❌ | ✅ |
React本身也推荐使用受控组件,在使用场景上受控组件也要强于非受控组件。
Less VS Css in js
- Less
| 特性 | Less | Css in js |
|---|---|---|
| 全局污染 | 强 | 无 |
| 动态性 | 无 | 强 |
| rem布局 | ✅ | ❗️大部分框架都支持 |
| 可读性 | 高 | 高 |
| 上手成本 | 低 | 高 |
| postcss | 支持 | ❗️有替代产品 |
| tree-shaking | 需配置 | ✅ |
| js变量 | ❌ | ✅ |
| 性能 | ✅ | ❌ |
Less和Css in js方案各有特色,但考虑到上手成本、熟悉度、性能优劣,选择Less方案
构建方案
产物类型
至于要用什么类型的产物,首先要考虑我们在开发过程中都会以什么样的方式引用它,大概有三种方式
- 在html中以
- 用import的方式引入。
- 用require的方式引用。
所以基于三种使用方式需要有三种产物,目前主流的js模块有以下几种,通过对比他们的特性,可以选出我们需要哪些打包产物的类型。
| 格式 | 特性 | 浏览器使用 | 代码使用 |
|---|---|---|---|
| UMD | 通用模块定义,兼容AMD、CommonJS和全局变量 | 通过<script>标签引入 | define或全局变量 |
| CJS | CommonJS模块规范,同步加载 | 不直接支持,需通过Node.js环境 | require和module.exports |
| ESM | ECMAScript模块规范,原生支持ES6+ | 通过<script type="module">引入 | import和export |
| AMD | 异步模块定义,依赖前置 | 通过RequireJS等模块加载器 | define和require |
| CMD | 类似AMD,但依赖就近、延迟执行 | 通过SeaJS等模块加载器 | define和require |
基于以上的对比,我们只需要产出UMD、CJS、ESM格式的产物就能满足我们的诉求。
构建配置
- UMD
umd用webpack打包成单独的文件即可,具体的配置如下:
- ESM & CJS
esm格式可用babel直接打包
ESM格式需要useEsModules设置为true,CJS格式需要useEsModules设置为false;
- webpack配置
在package中针对打包出来的三种不同的产物(ems、umd、cjs)配置对应规范的入口。
测试方案
单元测试
ReactTestingLibrary + jest
e2e测试
cypress
组件文档
组件库文档的开源框架有很多,都有各自的优势,目前storybook使用的较多,而且各方面都比较均衡
| 特性 | Storybook | Docusaurus | Docz | Styleguidist |
|---|---|---|---|---|
| 框架支持 | React, Vue, Angular等 | React | React | React, Vue |
| 主要用途 | UI组件开发、测试与展示 | 静态网站生成、文档编写 | React组件文档生成 | 组件库文档生成、样式指南 |
| 文档生成方式 | 自动从组件生成 | Markdown支持 | Markdown + MDX,自动从组件生成 | 自动从组件生成,Markdown支持 |
| 实时预览 | 支持 | 不直接支持,但可通过React实现 | 支持,内置热重载 | 支持,实时编辑和HMR |
| 定制化能力 | 中等,支持配置和插件 | 高,支持主题和插件定制 | 高,支持主题和配置定制 | 高,支持样式、布局和插件定制 |
| 学习曲线 | 中等,需要熟悉组件开发 | 低,易于上手 | 中等,需要熟悉React和Markdown | 中等,需要熟悉组件开发和Markdown |
| 社区支持 | 活跃,广泛应用 | 活跃,Facebook支持 | 活跃,但可能随时间波动 | 活跃,适用于大型组件库项目 |
| 扩展性 | 较好,支持插件系统 | 非常好,丰富的插件生态 | 较好,但可能不如其他工具灵活 | 较好,支持自定义插件 |
| 性能 | 高效,适合组件开发 | 优秀,静态网站加载速度快 | 优秀,基于Gatsby构建 | 高效,适合实时预览和文档生成 |
| 适合场景 | UI组件库开发、设计系统 | 技术文档、项目文档、博客等 | React组件库文档、演示 | React/Vue组件库文档、样式指南 |
六、总结
物料(组件库)在前端提效策略中几乎是最重要,最有效的途径!物料作为构建用户界面的基本单元,直接影响着开发速度,还关联到产品的最终用户体验。一个设计良好、高度可复用的组件库,能够显著减少重复工作,提升开发团队的生产力。相反一个设计有缺陷,扩展性不好、场景适配不强的组件会变得很鸡肋,使用率不高。所以在做组件的规划、评审以及与设计师的沟通的环节至关重要。另外物料也是属于基建能力,在稳定性、安全性上非常重要。