此文章是由个人对该技术的理解和解读官网翻译而来,不免有不太恰当的地方,望多多指教,共同学习。
react-styleguidist 会把所有组件都显示在浏览器的一个页面上,包含 props 文档,用法示例以及一个独立开发组件的环境。 在 Styleguidist 中,可以在 Markdown 中编写示例,每个代码段都会立即呈现;
原理
Styleguidist 会加载组件,使用 react-docgen 生成文档,可能需要更改代码才能正常工作。
React-docgen 会把组件作为静态文本文件读取,然后类似于查找 React 组件的模式去查找组件(例如类或函数声明)。React-docgen 不会运行任何 JavaScript 代码,因此,如果组件是动态生成,或包装在高阶组件中,或拆分为多个文件,react-docgen 可能无法理解。
- React-docgen 支持通过 React.createClass,ES6 classes 和函数组件定义的组件;
- React-docgen支持Flow和TypeScript注释;
在某些情况下,可以通过导出两个组件 欺骗 Styleguidist和react-docgen:
- 作为命名导出的基本组件
- 作为默认导出的增强组件
import React from 'react'
import CSSModules from 'react-css-modules'
import styles from './Button.css'
// Base component will be used by react-docgen to generate documentation
export function Button({ color, size, children }) {
/* ... */
}
// Enhanced component will be used when you write <Button /> in your example files
export default CSSModules(Button, styles)
开始
安装 Styleguidist:
// npm
npm install --save react-styleguidist
// yarn
yarn add react-styleguidist
配置package.json脚本
"scripts": {
"styleguide": "NODE_ENV=development styleguidist server",
"styleguide:build": "NODE_ENV=production styleguidist build",
}
运行Styleguidist
npm run styleguide //启动styleguidist开发服务器
npm run styleguide:build //构建生产HTML版本
组件生成文档
Styleguidist 根据组件中的注释,propTypes 声明和Readerme.md
为组件生成文档。
解析props
默认行为:Styleguidist 从 propType s中获取 props 生成一个表格,并根据 props 的注释显示组件的说明,根据 defaultProps 获取 props 的默认值。
import React from 'react'
import PropTypes from 'prop-types'
/**
* General component description in JSDoc format. Markdown is *supported*.
*/
export default class Button extends React.Component {
static propTypes = {
/** Description of prop "foo". */
foo: PropTypes.number,
/** Description of prop "baz". */
baz: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}
static defaultProps = {
foo: 42
}
render() {
/* ... */
}
}
修改默认行为
可以使用propsParser
和resolver
更改解析props默认行为;
propsParser: Function
此方法可以重新个从源文件如何解析props。默认是使用react-docgen进行解析。
module.exports = {
propsParser(filePath, source, resolver, handlers) {
return require('react-docgen').parse(source, resolver, handlers)
}
}
resolver
此方法可以确定哪些类/组件需要解析。默认行为是在每个文件中查找所有导出的组件,可以配置它以让所有查找到的组件使用自定义解析方法。
module.exports = {
resolver: require('react-docgen').resolver
.findAllComponentDefinitions
}
组件的 PropType 和文档注释由 react-docgen 库解析, 可以使用 updateDocs 函数对其进行修改。
有关解析props的更多信息,可参见react-docgen文档。
示例文件查找与编写
默认行为:Styleguidist 默认在组件文件夹下查找 Readme.md
或 [ComponentName].md
的文件并生成示例文件。
带有 js,jsx 或 javascript 标记的代码块将被呈现为带有交互式的 React 组件。为了向后兼容,不带语言标签的代码块也会展示成带有交互式的 React 组件。
React component example:
```js
<Button size="large">Push Me</Button>
```
You can add a custom props to an example wrapper:
```js { "props": { "className": "checks" } }
<Button>I’m transparent!</Button>
```
To render an example as highlighted source code add a `static` modifier:
```jsx static
import React from 'react';
```
修改默认行为
可以通过 getExampleFilename
自定义 md 文件名字,此方法通过提供的组建文件路径返回新的 md 文件路径。
用 ComponentName.examples.md
代替 Readme.md
:
module.exports = {
getExampleFilename(componentPath) {
return componentPath.replace(/\.jsx?$/, '.examples.md')
}
}
关联其他示例文件
可以使用@example doclet
语法将其他示例文件与组件关联。
Button 组件有一个从 extra.examples.md 文件加载的示例:
/**
* Component is described here.
*
* @example ./extra.examples.md
*/
export default class Button extends React.Component {
// ...
}
写 code
- 导入依赖项: 通过 import 导入
- 管理状态:每个示例都是一个 function 组件,可以使用 useState Hook 来处理状态。
// ```jsx inside Markdown
import React from 'react'
import Button from 'rsg-example/components/Button'
import Placeholder from 'rsg-example/components/Placeholder'
;<Button size="large">Push Me</Button>
// ```jsx inside Markdown
const [isOpen, setIsOpen] = React.useState(false)
;<div>
<button onClick={() => setIsOpen(true)}>Open</button>
<Modal isOpen={isOpen}>
<h1>Hallo!</h1>
<button onClick={() => setIsOpen(false)}>Close</button>
</Modal>
</div>
moduleAliases
定义模块的别名,可以在示例文件中导入这些别名,以使示例代码更加实际和可复制;
const path = require('path');
module.exports = {
moduleAliases: {
'rsg-example': path.resolve(__dirname, 'src')
}
}
- Markdown语法都被支持;
- 如果需要在文档中显示一些不想被呈现为交互式的JavaScript代码,则可以将static修饰符与语言标签一起使用;
- Styleguidist 通过 Bublé 运行ES6代码, 大多数ES6功能都支持;
- rsg-example模块是由moduleAliases 选项定义的别名;
- 如果需要更复杂的演示,通常最好将其定义在一个单独的JavaScript文件中,然后将其导入Markdown中
公共方法
默认情况下,组件所具有的任何方法均被视为私有方法,不会被发布。使用JSDoc @public
标记方法,就可以使其成为公共方法并会在文档中发布。
/**
* @param {string} name
* @public
*/
getName(name) {
// ...
}
隐藏props
默认情况下,组件所有的props都是公开并可发布。在某些情况下代码存在某props, 希望文档中不展示这个props,使用JSDoc @ignore
标记props,就可以将其从文档中删除。
Button.propTypes = {
/**
* A prop that should not be visible in the documentation.
* @ignore
*/
hiddenProp: React.PropTypes.string
}
定位组件
查找组件
默认情况下,Styleguidist将使用此模式定位组件:src/components/**/*.{js,jsx,ts,tsx}
。
例如:
src/components/Button.js
src/components/Button/Button.js
src/components/Button/index.js
但是会忽略 tests 文件:
__tests__
文件夹- 文件名里包含
.test.js
或.spec.js
(类似于:.jsx
,.ts
,.tsx
)
修改默认查找方式
在styleguide.config.js
文件里的配置components
可以修改组件查找方式来适应不同的项目;
例如:如果组件路径是components/Button/Button.js
,为了简化导入在components/Button/index.js
中再次导出(export { default } from './Button'
),【为了让components/Button
替代components/Button/Button
】, 这时候需要跳过 index.js。
module.exports = {
components: 'src/components/**/[A-Z]*.js'
}
使用ignore
可将某些文件从样式指南中排除
ignore:String[]
默认值:
['**/__tests__/**', '**/*.test.{js,jsx,ts,tsx}', '**/*.spec.{js,jsx,ts,tsx}', '**/*.d.ts']
使用getComponentPathLine
更改组件导入路径
getComponentPathLine: Function
默认值: 组件文件名;
返回值:返回组件路径;
例如: 从components/Button
中导入Button,而不是components/Button /Button.js
const path = require('path');
module.exports = {
getComponentPathLine(componentPath) {
const name = path.basename(componentPath, '.js')
const dir = path.dirname(componentPath)
return `import ${name} from '${dir}';`
}
}
所有路径都相对于config文件夹;
加载组件
Styleguidist 加载组件并暴露在全局以供示例使用。
标识符
Styleguidist 默认使用组件的 displayName 作为标识符。如果它不能理解displayName(displayName动态生成),它将退回到它可以理解的东西。
Sections
Sections: Array[{}]
将组件分组,或将额外的Markdown文档添加到样式指南中。
Sections数组的每一项属性值如下:
属性名 | 描述 |
---|---|
name | Sections 标题 |
content | 包含概述内容的Markdown文件的位置 |
components | 可以是glob模式字符串,组件路径数组,glob模式字符串数组,返回一个组件数组的函数或返回glob模式字符串的函数。规则与根选项components 相同; |
sections | 嵌套的Sections数组(可以再嵌套) |
description | section描述 |
sectionDepth | 单个页面的小节数,仅在pagePerSection 中可用 |
exampleMode | 代码示例的初始状态,使用 exampleMode |
usageMode | props和方法的初始状态, 使用 usageMode |
ignore | 需要忽略的文件,值可以是字符串或数组; |
href | navigate的URL(不是导航到Sections 内容的URL) |
external | 如果设置,会打开一个新页面 |
expand | 在常规设置中将tocMode 设置为collapse (折叠)时,确认是否应该被展开; |
- 上述所有字段都是可选的;
// styleguide.config.js
module.exports = {
sections: [
{
name: 'Introduction',
content: 'docs/introduction.md'
},
{
name: 'Documentation',
sections: [
{
name: 'Installation',
content: 'docs/installation.md',
description: 'The description for the installation section'
},
{
name: 'Configuration',
content: 'docs/configuration.md'
},
{
name: 'Live Demo',
external: true,
href: 'http://example.com'
}
]
},
{
name: 'UI Components',
content: 'docs/ui.md',
components: 'lib/components/ui/*.js',
exampleMode: 'expand', // 'hide' | 'collapse' | 'expand'
usageMode: 'expand' // 'hide' | 'collapse' | 'expand'
}
]
}
配置webpack
Styleguidist 依赖 webpack,使用 webpack 确定如何加载项目的文件,但项目也不一定非要配置 webpack。
默认情况下,Styleguidist 会在项目的根目录中查找webpack.config.js
并使用它。
自定义Webpack配置
如果 webpack 配置在其他位置,则需要手动加载:
module.exports = {
webpackConfig: require('./configs/webpack.js')
}
也可以 merge 多个 webpack 配置:
module.exports = {
webpackConfig: Object.assign({}, require('./configs/webpack.js'), {
/* Custom config options */
})
}
- 配置
entry
,externals
,output
,watch
, 和stats
会被忽略。生产上,devtoo
也会被忽略;- 插件
CommonsChunkPlugins
,HtmlWebpackPlugin
,MiniHtmlWebpackPlugin
,UglifyJsPlugin
,TerserPlugin
,HotModuleReplacementPlugin
会被忽略, 因为 Styleguidist 已经引入了他们,再次引入会影响 Styleguidist;- 如果loaders 不起作用,请尝试包含和排除绝对路径;
styleguide配置属性
属性名 | 类型 | 含义 |
---|---|---|
title | String | 设置网页标题 |
serverPort | Number | 端口号 |
require | Array | 添加用户自定义的js, CSS 或 polyfills |
assetsDir | String | 资源文件名 |
styleguideDir | String | 定义styleguidist构建命令生成的静态HTML的文件夹;默认值:styleguide |
getComponentPathLine | Function | 获取组件加载路径 |
template | Object / Function | 更改应用程序的HTML。 可以添加favicon,meta 标签, 内嵌JavaScript或CSS的对象。 |
styles | Object / String / Function | 自定义Styleguidist 示例组件的样式;配置的文件路径是相对配置文件或者绝对路径。 |
theme | Object / String | 自定义Styleguidist 示例组件 UI字体,颜色等; 配置的文件路径是相对配置文件或者绝对路径。 |
sections | Array[{}] | 设置组件分组 |
styleguideComponents | Object | 重写被用于渲染到浏览器的styleguide React组件; |
webpackConfig | Object / String | 自定义webpack配置; |
更多配置选项查看官方文档:configuration
使用
如何在样式指南里隐藏某组件,在示例里可以使用此组件?
通过skipComponentsWithoutExample
属性,不给需要忽略的组件添加示例文件(默认为Readme.md)。
skipComponentsWithoutExample: Boolean
默认值:false
如果没有示例文件就忽略此组件;除非手动require
它们,否则其他示例也无法访问这些组件。
在一个示例中引用被忽略的组件:
// /Readme.md
// ```jsx
import Button from '../common/Button'
;<Button>Push Me Tender</Button>
在所有示例中可以使用被忽略的组件:
// styleguide.config.js
module.exports = {
require: [path.resolve(__dirname, 'styleguide/setup.js')]
}
// styleguide/setup.js
import Button from './src/components/common/Button'
global.Button = Button
每个示例不引入Button 组件,也可以使用 Button 组件;
如何更改样式指南的布局?
任何 Styleguidist React 组件都可以被替换。
在大多数情况下,只需要替换*Renderer
组件 - 所有HTML都由这些组件呈现。查看所有可用的组件
有时还会修改组件包装器Wrapper
组件 - 包装每个示例组件。默认情况下按原样呈现children
,但是可以使用它提供自定义逻辑渲染 children
。
替换 Wrapper
替换默认 Wrapper 组件,用 react-intl 的 IntlProvider 代替:
无法包装整个样式指南,因为每个示例都是在浏览器中单独编译的。
// styleguide.config.js
const path = require('path');
module.exports = {
styleguideComponents: {
Wrapper: path.join(__dirname, 'src/styleguide/Wrapper')
}
}
// src/styleguide/Wrapper.js
import React, { Component } from 'react';
import { IntlProvider } from 'react-intl';
export default class Wrapper extends Component {
render() {
return (
<IntlProvider locale="en">{this.props.children}</IntlProvider>
)
}
}
替换 StyleGuideRenderer
const path = require('path')
module.exports = {
styleguideComponents: {
StyleGuideRenderer: path.join(
__dirname,
'src/styleguide/StyleGuideRenderer'
)
}
}
// src/styleguide/StyleGuideRenderer.js
import React from 'react'
const StyleGuideRenderer = ({
title,
version,
homepageUrl,
components,
toc,
hasSidebar
}) => (
<div className="root">
<h1>{title}</h1>
{version && <h2>{version}</h2>}
<main className="wrapper">
<div className="content">
{components}
<footer className="footer">
<Markdown
text={`Created with [React Styleguidist](${homepageUrl})`}
/>
</footer>
</div>
{hasSidebar && <div className="sidebar">{toc}</div>}
</main>
</div>
)
如何更改样式指南的样式?
有两个属性可以更改样式指南的样式: theme 和 styles。
- theme:可以更改字体,颜色等。
- styles:可以调整任何Styleguidist组件的样式;
// styleguide.config.js
module.exports = {
theme: {
color: {
link: 'firebrick',
linkHover: 'salmon'
},
fontFamily: {
base: '"Comic Sans MS", "Comic Sans", cursive'
}
},
styles: {
Logo: {
// the LogoRenderer component
logo: {
animation: '$blink ease-in-out 300ms infinite'
},
'@keyframes blink': {
to: { opacity: 0 }
}
}
}
}
可以将 theme 和 styles 的对象值存储在单独的文件中, 在配置文件中引入文件路径,这样允许热模块更换(HMR)。
每次修改 theme.js 或 styles.js 都会触发 HMR,从而更新浏览器中的styleguide。
上面的相同示例将转换为:
// styleguide.config.js
module.exports = {
// ...
styles: './styleguide/styles.js',
theme: './styleguide/themes.js'
}
// ./styleguide/theme.js
module.exports = {
color: {
link: 'firebrick',
linkHover: 'salmon'
},
fontFamily: {
base: '"Comic Sans MS", "Comic Sans", cursive'
}
}
// ./styleguide/styles.js
module.exports = {
Logo: {
// the LogoRenderer component
logo: {
animation: '$blink ease-in-out 300ms infinite'
},
'@keyframes blink': {
to: { opacity: 0 }
}
}
}
- 查看可用的 theme 变量
- styles 使用的是以下几个JSS plugin: jss-isolate, jss-nested, jss-camel-case, jss-default-unit, jss-compose 和 jss-global
如何给组件添加全局样式?
通过StyleGuide
属性,以jss-global
api 的形式实现。
这种方法不会在样式指南UI上设置样式。
// styleguide.config.js
module.exports = {
components: 'src/components/**/[A-Z]*.js',
styles: {
StyleGuide: {
'@global body': {
fontFamily: 'Helvetica',
fontWeight: bold,
}
}
}
}
相当于:
body {
font-family: 'Helvetica';
font-weight: bold,
}
如何添加自定义JavaScript, CSS, polyfill?
使用 require 属性:
// styleguide.config.js
const path = require('path');
module.exports = {
require: [
'babel-polyfill',
path.join(__dirname, 'path/to/script.js'),
path.join(__dirname, 'path/to/styles.css')
]
}
如何添加外部JavaScript和CSS文件?
使用 template 属性。
template
更改样式指南的HTML。属性值支持 Object 和 Function 两种。
- Object:一个具有添加网页图标,元标记,内联JavaScript或CSS等的对象。查看 @vxna/mini-html-webpack-template docs.
- Function:返回HTML字符串的函数。查看 mini-html-webpack-plugin docs.
// styleguide.config.js
module.exports = {
template: {
head: {
scripts: [
{
src: 'assets/js/babelHelpers.min.js'
}
],
links: [
{
rel: 'stylesheet',
href: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css'
}
]
}
}
}
与 require 属性相比,template 里配置的 scripts 和 links 在浏览器中运行,而不是在webpack构建过程中运行。这对于组件产生副作用的脚本很有用。在这种情况下,Babel输出需要功能正常。
如何在样式指南中使用CSS动画?
由于内部关键帧作用域,CSS规则中的animation属性没有直接使用其关键帧动画的名称。 如果使用CSS动画,必须在渲染器对象的根部定义其关键帧。
// styleguide.config.js
module.exports = {
styles: {
// the LogoRenderer component
Logo: {
'@keyframes blink': {
to: { opacity: 0 }
}
}
}
}
如何从Google字体添加字体?
使用 template 和 theme属性:
// styleguide.config.js
module.exports = {
template: {
head: {
links: [
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css?family=Roboto'
}
]
}
},
theme: {
fontFamily: {
base: '"Roboto", sans-serif'
}
}
}
如何在示例中使用ref
?
把ref props作为函数使用,将参数赋值给局部变量:
// Button/Readme.md
const [value, setValue] = React.useState('')
let textareaRef
;<div>
<Button onClick={() => textareaRef.insertAtCursor('Pizza')}>
Insert
</Button>
<Textarea
value={value}
onChange={e lili=> setValue(e.target.value)}
ref={ref => (textareaRef = ref)}
/>
</div>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。