文/无码
前言
业务快速发展,业务组件库也在快速迭代。当组件Props发生变化,开发需要额外的消耗一定精力去保持代码和文档的统一。如下表是一个Select
组件的API文档(字段摘自fusion的Select组件)。
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ----------------------- | ---------------------------------------- | ------------------------------ | -------------------------- | ------ |
| dataSource _(required)_ | 传入的数据源,可以动态渲染子项 | `string[]` | | |
| onChange | Select发生改变时触发的回调 | `(item: string) => void` | | |
| readOnly | 是否只读,只读模式下可以展开弹层但不能选 | `boolean` | false, true | false |
| size | 选择器尺寸 | `"medium" | "small" | "large"` | "medium", "small", "large" | medium |
| value | 当前值,用于受控模式 | `string | number` | string, number | |
而组内年初开始全面拥抱TypeScript
,利用其静态类型检查与代码提示,来增强开发的生产力。在开发组件过程中也逐渐抛弃了propTypes,会选择去定义对应的Props interface(后文也以此表示组件props的TS类型),例如上面提到的Select组件的定义。
import * as React from 'react';
export interface ISelectProps {
/**
* 传入的数据源,可以动态渲染子项
*/
dataSource: string[];
/**
* Select发生改变时触发的回调
*/
onChange?: (item: string) => void;
/**
* 是否只读,只读模式下可以展开弹层但不能选
*/
readOnly?: boolean;
/**
* 选择器尺寸
*/
size?: 'small' | 'medium' | 'large';
/**
* 当前值,用于受控模式
*/
value?: string | number;
}
/**
* 选择器
*/
class Select extends React.Component<ISelectProps> {
static defaultProps = {
readOnly: false,
size: 'medium',
};
render() {
return <div>Test</div>;
}
}
export default Select;
可以看出组件API文档实际上是组件Props的映射(一般是Markdown),而当我们有这么一份Props interface时,应该有某种手段可以帮助我们自动产生对应的API文档。 本文的目标是通过组件的Props interface快速生成对应的API表格,介绍的工具 react-docgen-typescript
是一款解析React TS组件AST的库,基于此开发VSCode插件和remark插件,可在项目中自动化生成组件文档。
调研与分析
我们回过头来看下API文档中props的信息可以来自代码哪些地方。
从上表中可以发现,组件props数据可以来源自多处,且propTypes和Props interface都具备对props的完整描述能力(从另一角度看,TS应用的场合不仅仅是React组件,在类型复用上有propTypes难以比拟的优势)。 React组件和Props interface也是TS代码的一部分,借助TS的解析器应该能拿到对应信息,再通过处理能转化为我们需要的数据。初步联想到应该有这么几步:
- parse 源码得到 TypeScript AST
- 通过AST判断导出的Class/Function是否React组件
- 提取出其中组件 props 的类型以及对于Interface的注释内容,产生另一棵只包含组件信息的树
- 再将组件信息树转为Markdown代码
类似下图
在工具的调研过程中,发现了现有的类似解决方案,目前已有的工具中,有一些值得借鉴的技术点,主要有如下几个方案:
- 基于react-docgen库,扩展支持子组件,增强对JSDoc的支持,如支持@default、@enumdesc等注释
- 使用remark生成markdown
- 基于react-docgen,扩展了对多文件的处理,并且对各文件导出的interface做分类,但不支持从其它文件引入ts类型
- 基于react-docgen-typescript和webpack,动态生成组件文档
目前方案主要使用react-docgen或react-docgen-typescript,通过阅读两个库关键代码,和运行调试对应demo,了解到二者有很大区别。
react-docgen 和 react-docgen-typescript对比
react-docgen
- 来自Facebook开源
- 基于Babel解析源码,对propTypes支持良好
- 虽然新版本支持 TypeScript,但从其它文件导入的类型信息无法被获取
- 不解析JSDoc部分,整个注释都作为描述部分,不过可以添加自己handler来补充解析
react-docgen-typescript
- 来自styleguidist开源,主要目标是服务TS React组件的API文档生成
- 基于TS解析源码,不支持propTypes,Props interface继承的类型都可以拿到
- 会读取JSDoc的@type、@default作为类型和默认值信息
两个库对字段信息读取对比
乍一看react-docgen功能更加全面,也支持TS组件,为什么不选react-docgen? react-docgen基于Babel解析源码,只获取被解析组件文件内容,无法做到像TS一样分析出依赖。举个常见的场景,当继承来自其他文件的类型时,Babel理论上就无法获取其它文件的信息,造成最后输出的props类型缺失。
如下定义ICustomSelectProps,组件继承了ISelectProps属性,因为ISelectProps在另一个文件中,react-docgen只能获取到customProp字段,而ISelectProps中定义的dataSource、onChange等字段都丢失了。
import * as React from "react";
import { ISelectProps } from './Select';
interface ICustomSelectProps extends ISelectProps {
customProp: string;
}
export class CustomSelect extends React.Component<ICustomSelectProps> {
render() {
return <div>Test</div>;
}
}
综上,当我们全面使用TypeScript后,使用react-docgen-typescript来做组件信息输出更加合适。
设计与实现
有了react-docgen-typescript整体难度减少了很多,输入组件源码就可以得到一颗组件信息树,然后接下来的问题是怎么应用这颗树,导出组件文档。
whale-component-docgen
首先想到是直接从组件导出文档,常见的组件目录结构如下,当目录符合下面规范时
component // 组件名称, 比如 biz-button
├── src // 【必选】组件源码
│ ├── index.js // 【必选】组件出口文件
│ └── index.scss // 【必选】仅包含组件自身样式的源码文件
├── README.md // 【必选】组件说明及API
└── package.json // 【必选】组件 package.json
可以结合组件出口文件和package.json快速输出README.md文件。同时参考物料的README规范,于是就有了@alife/whale-component-docgen 安装完后,在物料目录下执行whale-component-docgen
命令,就能获得如下符合物料规范标准的README文件
# test-select
业务组件描述
## 安装方法
```sh
$ tnpm install test-select --save
```
## API
### Select
选择器
#### Props
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ----------------------- | ---------------------------------------- | ------------------------------ | -------------------------- | ------ |
| dataSource _(required)_ | 传入的数据源,可以动态渲染子项 | `string[]` | | |
| onChange | Select发生改变时触发的回调 | `(item: string) => void` | | |
| readOnly | 是否只读,只读模式下可以展开弹层但不能选 | `boolean` | false, true | false |
| size | 选择器尺寸 | `"medium" | "small" | "large"` | "medium", "small", "large" | medium |
| value | 当前值,用于受控模式 | `string | number` | string, number | |
如果生成失败,注意文档生成有下面两个前提
1. 需要安装@types/react
1. 引入React方式需要为import * as React from 'react';
Remark插件: remark-react-docgen-typescript
全量生成文档的优点是简单,但有些复杂物料的README中会包含其他内容,一股脑的全量生成是不行的。
remark是现在最受欢迎的Markdown处理器,remark-react-docgen-typescript是remark的插件。
[cytle/remark-react-docgen-typescript
github.com](link.zhihu.com/?target=htt…)
如同Markdown可以引入图片,使用该插件后可以按如下语法引入组件文档
[Select](./src/Select.tsx "react-docgen-typescript:")
上面的whale-component-docgen也集成了remark-react-docgen-typescript,通过指定Markdown文件可以转换文档
whale-component-docgen README.md
remark也有对应的cli和vscode工具
VSCode插件
对比vscode-remark是在原有Markdown文档上格式化代码,在灵活性上还有些不足,其实可以更直接明了的生成/插入组件文档片段。
[React Docgen TypeScript - Visual Studio Marketplace
marketplace.visualstudio.com](link.zhihu.com/?target=htt…)
React Docgen TypeScript就是这样一个插件,提供两种方式获取组件文档
- tsx文件生成文档到剪贴板
- md文件选择文件插入组件文档
tsx文件生成文档到剪贴板
md文件选择文件插入组件文档
结语
TypeScript给js带来了类型,做到静态类型检查和代码提示,经过上面一顿复杂的折腾,把类型又用了一遍(一鱼三吃)。整个过程中,我已经拿到了组件中的所有想要拿到的数据,除了生成物料文档外,围绕这些数据可以做其它工具,如
- 多组件整站文档生成,解决目前没解决的子类型展示问题
- LowCode 组件配置项生成,供搭建平台使用