10

此文章是由个人对该技术的理解和解读官网翻译而来,不免有不太恰当的地方,望多多指教,共同学习。

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() {
    /* ... */
  }
}

修改默认行为

可以使用propsParserresolver更改解析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数组的每一项属性值如下:

属性名描述
nameSections 标题
content包含概述内容的Markdown文件的位置
components可以是glob模式字符串,组件路径数组,glob模式字符串数组,返回一个组件数组的函数或返回glob模式字符串的函数。规则与根选项components相同;
sections嵌套的Sections数组(可以再嵌套)
descriptionsection描述
sectionDepth单个页面的小节数,仅在pagePerSection中可用
exampleMode代码示例的初始状态,使用 exampleMode
usageModeprops和方法的初始状态, 使用 usageMode
ignore需要忽略的文件,值可以是字符串或数组;
hrefnavigate的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配置属性

属性名类型含义
titleString设置网页标题
serverPortNumber端口号
requireArray添加用户自定义的js, CSS 或 polyfills
assetsDirString资源文件名
styleguideDirString定义styleguidist构建命令生成的静态HTML的文件夹;默认值:styleguide
getComponentPathLineFunction获取组件加载路径
templateObject / Function更改应用程序的HTML。 可以添加favicon,meta 标签, 内嵌JavaScript或CSS的对象。
stylesObject / String / Function自定义Styleguidist 示例组件的样式;配置的文件路径是相对配置文件或者绝对路径。
themeObject / String自定义Styleguidist 示例组件 UI字体,颜色等; 配置的文件路径是相对配置文件或者绝对路径。
sectionsArray[{}]设置组件分组
styleguideComponentsObject重写被用于渲染到浏览器的styleguide React组件;
webpackConfigObject / 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 }
    }
  }
}

如何给组件添加全局样式?

通过StyleGuide属性,以jss-globalapi 的形式实现。

这种方法不会在样式指南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 两种。

// 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>

时倾
794 声望2.4k 粉丝

把梦想放在心中