漫谈 React 组件库开发(二):组件库最佳实践

在 React 大生态下,一个比较成熟的前端团队,都会面对一个问题:如何提高团队的开发效率?

一个系统拥有大量的业务场景和业务代码,相似的页面和代码层出不穷,如何管理和抽象这些相似的代码和模块,这肯定是诸多团队都会遇到的问题。
不断的拷代码?还是抽象成 UI 组件或业务组件?显然后者更高效。

那么现在就面临一个选择:一是选择 React 生态中已有的组件库,例如 antDesign、Material-UI 等比较成熟的组件库;二是团队再开发一套属于自己的组件库。有赞前端团队选择了后者,产出并开源了 Zent ,Zent 提供了一整套基础的 UI 组件以及常用的业务组件,目前我们有 45+ 组件,这些组件都已经在有赞的各类 PC 业务中广泛使用。本文我们就来聊一聊如何开发一套优秀的 React 组件库以及一套完整组件库的构成。

一、选择开源?还是自己造轮子?

React 大环境里面有很多优秀的 UI 组件库,国内比较有名的 antDesign,国外的 Material-UI,都是比较稳定和优秀的组件库。那么我们为什么还要自己去开发一套组件库呢?原因大致如下:

  • 有赞各个业务线 PC 产品有独立的设计规范,包括但不限于组件样式、交互模式。
  • 有赞微商城、零售、美业等 PC 产品的业务场景较为复杂,需要深度定制某些通用的组件,如 DesignSKU 组件。
  • 需要同时支撑有赞多个业务部门的 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 为你的项目做以下三件事:

  1. 初始化 eslint/stylelint 配置文件,无论是 react 项目、vue 项目、es5 还是 es6 都提供了针对性的配置方案
  2. 安装 eslint/stylelint 及其依赖到当前项目的 node_modules 里
  3. 挂载 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-components

组件发包

组件发包只有拥有发包权限的人才能操作,Zent 是以组件库为单位发包的,yarn build 会将整个 Zent 的代码打包,使用命令 yarn publish 发包,在发包之前会跑组件测试,只有测试通过以后才能发包。

组件文档

一份好的 doc 是一个优秀组件库的标准,良好的文档能够提升组件库的整体品质和好感度,愿意花时间好好写 doc 的团队,那么他们产出的组件库应该也不会差到哪去,组件库文档维护也是组件库生命周期里重要的一环,有时候你甚至需要做到中英文双语 doc。

这里附上 Zent 组件库的 doc 地址:Zent

三、小结

在本文中,我们从组件的设计思路、编码规范、开发流程、测试、日常维护这五个方面阐述了如何构建一个 React 组件库,并且以 Zent 为例讲述了有赞是如何做的,任何一个组件库都需要的经过这个生命周期,但我们需要思考的是:如何营造一个良好的组件库生态环境? 我们需要想办法让更多的人参与其中,共同作为组件库的维护者,选择开源是为了给 React 生态环境做输出,在前端组件化已经成为了既定事实的今天,我们不需要重复的造轮子,而是需要在组件化方面尝试新的突破,脱离前端技术的束缚,站在工程师的高度去抽象自己手头的代码。组件化这条路上,我们还有很多事情要做,Zent 只是一个开始。