作者:凹凸曼 – 朱飞飞
背景
最近在接到一个开发 React 组件库的需要,组件库在开发过程中,刚写完一个组件打算给共事用,共事立马来了个灵魂拷问“啊?这个组件怎么用”。emmm,我寻思间接通知它下一次又忘了,还是老老实实写个文档吧。
文档写到一半,@#%#¥……#@麻烦死了。这么多组件,每个组件都须要有对应的文档,写起来太耗时了,手写文档比写个组件还麻烦。为了能快点完(xia)成(ban)任(hui)务(jia)。于是钻研下那些 优良的组件库 到底是怎么做的,看了下 Quark
夸克组件库的文档生成,大受启发,以下内容是讲讲对于如何优雅地偷懒并把组件文档都做好的。
为什么要主动生成文档
聊这个事件之前,咱们先看看文档心愿长什么样子
组件文档须要什么内容
- 提供组件的介绍阐明
- 提供组件的属性列表
propTypes
- 提供组件调用的案例
usage
- 提供组件调用的演示案例 / 源码
如果要把这些内容都通过 markdown
去写,写完消耗的工夫可能比做一个简略的组件还多,为了把更多的精力投入到开发更优质的组件当中,咱们须要 文档生成自动化。
文档自动化后能为咱们带来什么?
- 对立 文档格局,抹平不同开发者写文档的格局差别
- 节俭 写文档的工夫来做更多无意(tou)义(lan)的事件
咱们拿一个小案例来尝试一下
react-docgen
开始进入正题,先简略介绍下文档主动生成的配角 react-docgen
,官网对于它的介绍是这样的:
react-docgen 是一个 CLI 和工具箱,可帮忙从 React 组件中提取信息并从中生成文档。它应用 ast 类型和 @ babel / parser 将源解析为 AST,并提供解决此 AST 的办法以提取所需的信息。输入 / 返回值是一个 JSON blob / JavaScript 对象。
简略来说就是:它能提取组件的相干信息
装置
用 yarn 或 npm 装置模块:
yarn add react-docgen --dev
npm install --save-dev react-docgen
对于它的 API 能够参考官网文档 https://www.npmjs.com/package/react-docgen
偷偷再分享一个高级版的
react-styleguidist
https://github.com/styleguidist/react-styleguidist
例子
咱们先写一个人物的组件,外面蕴含 姓名
、 喜好
、 事件回调
// ./Persion/index.jsx
import React, {Component} from 'react'
import PropTypes from 'prop-types'
/**
* 人物组件
* @description 这是对于人物组件的形容内容
* @class Persion
* @extends {Component}
*/
class Persion extends Component {
/**
* 解决睡觉的回调
* @param {string} name 姓名
*/
handleSleep = (name) => {console.log(`${name} 开始睡觉 `)
this.props.onSleep()}
render() {const { name, hobbies} = this.props
return (<div onClick={this.handleSleep.bind(this, name)}>
<p> 姓名:{name}</p>
<p> 喜好:{hobbies.join(',')}</p>
</div>
)
}
}
Persion.propTypes = {
/**
* 姓名
*/
name: PropTypes.string.isRequired,
/**
* 喜好
*/
hobbies: PropTypes.array,
/**
* 睡觉的事件回调
*/
onSleep: PropTypes.func
}
Persion.defaultProps = {
name: '张三',
hobbies: ['睡觉', '打王者']
}
export default Persion
咱们定义了一个人物的组件,在组件 类正文 中形容了组件的根本信息, 同时在 propTypes
和defaultTypes
中也对组件的属性参数进行了定义和 属性正文
组件的根本信息都写的差不多了,那么咱们先开始应用 react-docgen
去提取组件的相干信息。
// ./docgen.js
const path = require('path')
const fs = require('fs-extra')
const reactDocs = require('react-docgen')
const prettier = require('prettier')
// 读取文件内容
const content = fs.readFileSync(path.resolve('./Persion/index.jsx'), 'utf-8')
// 提取组件信息
const componentInfo = reactDocs.parse(content)
// 打印信息
console.log(componentInfo)
这里写了一个简略的读取文件和解析的过程,并把提取到的信息打印进去,以下是组件信息提取后的内容 componentInfo
{
"description":"
人物组件
@description 这是对于人物组件的形容内容
@class Persion
@extends {Component}","displayName":"Persion","methods":[
{
"name":"handleSleep",
"docblock":"
解决睡觉的回调
@param name 姓名
","modifiers":[ ],
"params":[
{
"name":"name",
"description":"姓名",
"type":{"name":"string"},
"optional":false
}
],
"returns":null,
"description":"解决睡觉的回调"
}
],
"props":{
"name":{
"type":{"name":"string"},
"required":false,
"description":"姓名",
"defaultValue":{
"value":"'张三'",
"computed":false
}
},
"hobbies":{
"type":{"name":"array"},
"required":false,
"description":"喜好",
"defaultValue":{"value":"[' 睡觉 ',' 打王者 ']",
"computed":false
}
},
"onSleep":{
"type":{"name":"func"},
"required":false,
"description":"睡觉的事件回调"
}
}
}
对于 react-docgen 提取的信息中,解释下上面几个参数
displayName
组件名称description
组件的类正文methods
组件定义的办法props
组件的属性参数
其中这里的 props
是咱们组件文档的核心内容,在提取的内容中,曾经涵盖了属性的 属性名、属性形容、类型、默认值、是否必传。这些内容满足咱们浏览组件文档所须要的属性信息。
有了所需的 componentInfo
信息之后,下一步咱们须要把它转换成 markdown
(至于为什么要用 markdown 我就不解释了 8)
// ./docgen.js
// 生成 markdown 文档
fs.writeFileSync(path.resolve('./Persion/index.md'), commentToMarkDown(componentInfo))
// 把 react-docgen 提取的信息转换成 markdown 格局
function commentToMarkDown(componentInfo) {let { props} = componentInfo
const markdownInfo = renderMarkDown(props)
// 应用 prettier 丑化格局
const content = prettier.format(markdownInfo, {parser: 'markdown'})
return content
}
function renderMarkDown(props) {
return `## 参数 Props
| 属性 | 类型 | 默认值 | 必填 | 形容 |
| --- | --- | --- | --- | ---|
${Object.keys(props)
.map((key) => renderProp(key, props[key]))
.join('')}
`
}
function getType(type) {
const handler = {enum: (type) =>
type.value.map((item) => item.value.replace(/'/g,'')).join('\\|'),
union: (type) => type.value.map((item) => item.name).join('\\|')
}
if (typeof handler[type.name] === 'function') {return handler[type.name](type).replace(/\|/g, '')
} else {return type.name.replace(/\|/g, '')
}
}
// 渲染 1 行属性
function renderProp(
name,
{type = { name: '-'}, defaultValue = {value: '-'}, required, description }
) {return `| ${name} | ${getType(type)} | ${defaultValue.value.replace(
/\|/g,
'<span>|</span>'
)} | ${required ? '✓' : '✗'} | ${description || '-'} |
`
}
下面的转换 markdown 的代码其实做的事件比拟少,次要是以下几个步骤
- 遍历
props
对象中的每个属性, - 解析属性
prop
,提取属性名
、类型
、默认值
、必填
、形容
、生成对应的 markdown 表格行。 - 生成 markdown 内容,通过
prettier
丑化 markdown 代码。
通过转换后最终生成咱们这个 markdown 的文件
## 参数 Props
| 属性 | 类型 | 默认值 | 必填 | 形容 |
| ------- | ------ | ------------------ | ---- | -------------- |
| name | string | '张三' | ✗ | 姓名 |
| hobbies | array | ['睡觉', '打王者'] | ✗ | 喜好 |
| onSleep | func | - | ✗ | 睡觉的事件回调 |
拓展优化
这个案例只简略讲述了如何解析 props
并生成 markdown 的 参数 Props模块的流程,在事实我的项目中,以上流程还有很多能够优化的空间,咱们还能够通过很多自定义规定进行各种骚操作。
比方咱们不心愿把参数的 数据属性 (name、hobbies)和 回调属性(onSleep)都放到同一个 Props 表格中,咱们心愿能够进行属性上的分类。
在属性形容的正文中,咱们能够通过 @xx(或者 ¥%#@^!【】……你喜爱就好)进行不同的形容定义和分类,最终在属性解析的步骤中进行信息的深度的拆分解析分类,生成更加简单多元的文档。
通过一些革新后,咱们通过在 正文中增加不同规定的定义形容,失去更优雅好看的文档模块
Persion.propTypes = {
/**
* @text 姓名
* @category data
*/
name: PropTypes.string.isRequired,
/**
* @text 喜好
* @category data
*/
hobbies: PropTypes.array,
/**
* @text 睡觉的事件回调
* @category event
*/
onSleep: PropTypes.func
}
## 数据 Data
| 属性 | 类型 | 默认值 | 必填 | 形容 |
| ------- | ------ | ------------------ | ---- | ---- |
| name | string | '张三' | ✗ | 姓名 |
| hobbies | array | ['睡觉', '打王者'] | ✗ | 喜好 |
## 事件 Event
| 属性 | 类型 | 默认值 | 必填 | 形容 |
| ------- | ---- | ------ | ---- | -------------- |
| onSleep | func | - | ✗ | 睡觉的事件回调 |
当然还有很多比方 description
或者 methods
等都能够进行不同的解析并生成对应的 markdown 模块
,数据信息提取进去了,其实最终怎么进行ast
解析取决本身的具体业务要求。
小结
在日常开发的过程中,咱们除了组件的代码编写外,还有很多流程上、边角上的工作须要做,这些事件往往都比拟琐碎又必须要做。咱们多借助工具去解决咱们的工作中那些零星简略的工作,从而达到高(jiu)效(xiang)完(kuai)成(dian)工(xia)作(ban)的指标。开发者都是懈怠的(可能只有我??),不然怎么会有这么多自动化的产物呢~
参考资料:
[1] react-docgen 仓库文档 https://github.com/reactjs/react-docgen#readme
欢送关注凹凸实验室博客:aotu.io
或者关注凹凸实验室公众号(AOTULabs),不定时推送文章。