做者: 凹凸曼 - 朱飛飛react
最近在接到一個開發 React 組件庫的需求,組件庫在開發過程當中,剛寫完一個組件打算給同事用,同事立馬來了個靈魂拷問「啊?這個組件怎麼用」。emmm,我尋思直接告訴它下一次又忘了,仍是老老實實寫個文檔吧。git
文檔寫到一半,@#%#¥……#@麻煩死了。這麼多組件,每一個組件都須要有對應的文檔,寫起來太耗時了,手寫文檔比寫個組件還麻煩。爲了能快點完(xia)成(ban)任(hui)務(jia)。因而研究下那些優秀的組件庫究竟是怎麼作的,看了下Quark
夸克組件庫的文檔生成,大受啓發,如下內容是講講關於如何優雅地偷懶並把組件文檔都作好的。github
聊這個事情以前,咱們先看看文檔但願長什麼樣子npm
組件文檔須要什麼內容babel
propTypes
usage
若是要把這些內容都經過 markdown
去寫,寫完耗費的時間可能比作一個簡單的組件還多,爲了把更多的精力投入到開發更優質的組件當中,咱們須要文檔生成自動化。markdown
文檔自動化後能爲咱們帶來什麼?工具
咱們拿一個小案例來嘗試一下優化
開始進入正題,先簡單介紹下文檔自動生成的主角 react-docgen
,官方對於它的介紹是這樣的:ui
react-docgen 是一個 CLI 和工具箱,可幫助從 React 組件中提取信息並從中生成文檔。它使用 ast 類型和@ babel / parser 將源解析爲 AST,並提供處理此 AST 的方法以提取所需的信息。輸出/返回值是一個 JSON blob / JavaScript 對象。
簡單來講就是:它能提取組件的相關信息this
用 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 表格行。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),不定時推送文章。