漫谈 React 组件库开发(二):组件库最佳实践
在 React 大生态下,一个比较成熟的前端团队,都会面对一个问题:如何提高团队的开发效率?
一个系统拥有大量的业务场景和业务代码,相似的页面和代码层出不穷,如何管理和抽象这些相似的代码和模块,这肯定是诸多团队都会遇到的问题。
不断的拷代码?还是抽象成 UI 组件或业务组件?显然后者更高效。
那么现在就面临一个选择:一是选择 React 生态中已有的组件库,例如 antDesign、Material-UI 等比较成熟的组件库;二是团队再开发一套属于自己的组件库。有赞前端团队选择了后者,产出并开源了 Zent ,Zent 提供了一整套基础的 UI 组件以及常用的业务组件,目前我们有 45+ 组件,这些组件都已经在有赞的各类 PC 业务中广泛使用。本文我们就来聊一聊如何开发一套优秀的 React 组件库以及一套完整组件库的构成。
一、选择开源?还是自己造轮子?
React 大环境里面有很多优秀的 UI 组件库,国内比较有名的 antDesign,国外的 Material-UI,都是比较稳定和优秀的组件库。那么我们为什么还要自己去开发一套组件库呢?原因大致如下:
- 有赞各个业务线 PC 产品有独立的设计规范,包括但不限于组件样式、交互模式。
- 有赞微商城、零售、美业等 PC 产品的业务场景较为复杂,需要深度定制某些通用的组件,如
Design
和SKU
组件。 - 需要同时支撑有赞多个业务部门的 PC 产品。
- 团队成员以开源的模式参与组件库的开发,期间会有很多互相的讨论、碰撞,本身也是对团队的锻炼过程。
二、组件库构成
构建一个完整的组件库需要考虑:
- 组件设计思路
- 组件代码规范
- 组件开发流程
- 组件测试
- 组件维护(包括 PR / issue 管理、发包、文档)
1. 组件设计思路
组件是对一些具有相同业务场景和交互模式代码的抽象,组件库首先应该保证各个组件的视觉风格和交互规范保持一致,X
组件在 A
业务场景是一个交互,在 B
业务场景是另一个 UI 风格,这样就无法对 X
进行抽象,极大的增加了组件的构建成本。所以,设计组件之初,首先需要抽象和约定一套统一的视觉风格和交互规范。
其次,组件库的 props 定义需要具备足够的可扩展性,而且组件内部完全受控,保持组件具有统一的输入和输出,让我们来看一个 Button 的例子。
// Button is a react component of Zent
<Button
type="primary"
className="customer-classname"
loading={true}
disabled={false}
size="large"
onClick={this.handleClick}
>
{children}
</Button>
这是一个 Button 组件,我们定义了很多标记状态的 props,比如 type 表示 Button 的视觉风格,size 表示尺寸,disable 禁用,loading 状态等,这些状态在组件内部都不会维护 state,所有的状态由传入的 props 来决定,自定义 className 方便我们做样式自定义,children 方便我们自定义 Button 的显示内容。
Button 甚至提供了a
标签的功能,只要在 Button 上传入 props:href。
// Button as <a>
<Button
type="primary"
className="customer-classname"
href="https://www.youzan.com"
target="_blank"
>
有赞首页
</Button>
我们需要做几个约定:
- 组件所有状态受控于 props
- 组件 children 支持自定义 Dom 结构
- 不要写死组件内部的 Dom 结构
2. 组件代码规范
有赞前端内部组件库,使用的是开源 lint 工具–felint。
felint 是一个集成了 eslint、stylelint、git hook 的前端代码检查工具。felint 为你的项目做以下三件事:
- 初始化 eslint/stylelint 配置文件,无论是 react 项目、vue 项目、es5 还是 es6 都提供了针对性的配置方案
- 安装 eslint/stylelint 及其依赖到当前项目的 node_modules 里
- 挂载 git 钩子,在你提交代码时进行强制校验
具体使用可以参考官方 doc – felint 文档地址。
3. 组件开发流程
约定好组件的设计思路和代码规范以后,接下来我们就可以参与开发组件了,组件库的基本开发流程,包括以下几点:
- 组件初始化
- 组件 Coding
- 组件 Demo
Zent 里面有一个组件初始化命令:yarn new-component
,这个命令完成了组件大部分初始化工作,包括自动创建组件需要的目录和模版代码,添加组件 js 和 css 代码。然后,我们就可以开始写组件代码,代码风格和规范严格按照 lint 的规范编写,如果不符合规范,是不能提交代码的。写完组件以后,需要写组件 Demo 并运行,方式是本地启动 server 来运行组件 Demo,这个可以组件作为组件的调试工具。
4. 组件测试
js 单元测试框架有很多,chai、jest、mocha、karma 等等,Zent 组件库使用的是 jest + enzyme 的组合,下面来看一个例子:
// Button UI test
import { mount } from 'enzyme';
describe('Button', () => {
it('Button UI test', () => {
const wrapper = mount(<Button>OK</Button>);
expect(wrapper.hasClass('zent-btn')).toBe(true);
expect(wrapper.text()).to.equal('OK');
});
});
使用 jest 做 UI 测试有局限性,只能测试基本的 dom 结构 和样式,一些逻辑交互无法测到,只能覆盖大部分的情况。yarn test
用来执行测试脚本,测试结果会显示在终端。
5. 组件维护
组件日常维护占整个组件库生命周期的很大一部分,组件库做起来了以后,组件功能后续会不断迭代,也许是 bug fix,也可能是 new feature,这些组件的迭代我们通过 PR 和 issue 来管理,同时,我们需要管理好组件的 changelog。
总的来说,组件维护主要包括:PR / issue 的处理,发包和管理 changelog。
下面以 Zent 为例,来介绍一下 PR 规范。
PR 标题规则:[bug fix / breaking change / new feature] 组件名字:修改内容描述
- 前面方括号用来区分 PR / issue 的类型:bug fix - 组件 bug 修复;breaking change - 不兼容的改动;new feature - 新功能
- 修改内容尽可能言简意赅,总结 PR 的改动或者描述 issue
- 描述请用中文
- 组件名字请用英文,首字母大写
PR 用来生成 changelog,规范的 PR 有助于生成比较清晰的 changelog,一目了然,来看一下 Zent 的例子:
组件发包
组件发包只有拥有发包权限的人才能操作,Zent 是以组件库为单位发包的,yarn build
会将整个 Zent 的代码打包,使用命令 yarn publish
发包,在发包之前会跑组件测试,只有测试通过以后才能发包。
组件文档
一份好的 doc 是一个优秀组件库的标准,良好的文档能够提升组件库的整体品质和好感度,愿意花时间好好写 doc 的团队,那么他们产出的组件库应该也不会差到哪去,组件库文档维护也是组件库生命周期里重要的一环,有时候你甚至需要做到中英文双语 doc。
这里附上 Zent 组件库的 doc 地址:Zent。
三、小结
在本文中,我们从组件的设计思路、编码规范、开发流程、测试、日常维护这五个方面阐述了如何构建一个 React 组件库,并且以 Zent 为例讲述了有赞是如何做的,任何一个组件库都需要的经过这个生命周期,但我们需要思考的是:如何营造一个良好的组件库生态环境? 我们需要想办法让更多的人参与其中,共同作为组件库的维护者,选择开源是为了给 React 生态环境做输出,在前端组件化已经成为了既定事实的今天,我们不需要重复的造轮子,而是需要在组件化方面尝试新的突破,脱离前端技术的束缚,站在工程师的高度去抽象自己手头的代码。组件化这条路上,我们还有很多事情要做,Zent 只是一个开始。