作者: 凹凸曼 - 朱飞飞

背景

最近在接到一个开发 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 --devnpm install --save-dev react-docgen
对于它的 API 能够参考官网文档 https://www.npmjs.com/package/react-docgen

偷偷再分享一个高级版的 react-styleguidist https://github.com/styleguidist/react-styleguidist

例子

咱们先写一个人物的组件,外面蕴含 姓名喜好事件回调

// ./Persion/index.jsximport 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

咱们定义了一个人物的组件,在组件类正文中形容了组件的根本信息, 同时在propTypesdefaultTypes中也对组件的属性参数进行了定义和属性正文

组件的根本信息都写的差不多了,那么咱们先开始应用react-docgen去提取组件的相干信息。

// ./docgen.jsconst 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 的代码其实做的事件比拟少,次要是以下几个步骤

  1. 遍历props对象中的每个属性,
  2. 解析属性prop,提取属性名类型默认值必填形容、生成对应的 markdown 表格行。
  3. 生成 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),不定时推送文章。