create react app 如何通过 storybook 创建文档?

目的

希望能生成如下要求的文档,能便于组件的使用

  • 能实时渲染组件
  • 能查看代码示例
  • 能查看组件的参数说明
  • 列举组件的常用示例

环境

  • react@16.8
  • creat-react-app@4+
  • storybook@6.3
  • webpack@4+

安装

// 初始化
npx sb init

// 启动
npm run storybook

// 打包
npm run build-storybook

安装后会生成 .storybook 和 src/stories 的文件

  • .storybook/main.js 编译的配置
  • .storybook/preview.js 工具、插件等的配置
  • src/stories 默认的示例

配置

main.js

在基于 create-react-app 生成的项目中需要处理 webpack 配置

// main.js 所有配置项
{
    "stories": [] // 匹配 story 文档的规则
    "addons": [], // 所有的插件列表
    "webpackFinal": (config) => {}, // 可以扩展默认的 webpack 配置
    "babel": (options) => {} // 可以扩展默认的 bebel 配置
}
// ./.storybook/main.js
const webpackConfig = require("../config/webpack.config.dev.js")

module.exports = {
  stories: [
    // "../src/stories/*.stories.mdx",
    // "../src/stories/*.stories.@(js|jsx|ts|tsx)",
    "../src/components/**/*.stories.mdx",
    "../src/components/**/*.stories.js",
  ],
  addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
  webpackFinal: config => {
    // 移除默认的 css loader 规则
    config.module.rules.splice(7, 1)
    removeWebpackDefaultFileLoader(webpackConfig)

    let newConfig = {
      ...config,
      module: {
        ...config.module,
        rules: [...config.module.rules, ...webpackConfig.module.rules],
      },
    }

    // console.log("old config", newConfig.module)
    return newConfig
  },
  babel: async options => {
    // console.log(options.plugins)
    // 不返回值,默认使用 package.json 里的 babel 配置
  },
}

// 配置文件参见 webpack.dll.config.js
// 移除默认的 file loader
function removeWebpackDefaultFileLoader(webpackConfig) {
  webpackConfig.module.rules.forEach(rule => {
    const oneKey = "oneOf"
    if (Reflect.has(rule, oneKey)) {
      rule[oneKey].splice(rule[oneKey].length - 1, 1)
    }
  })
}

// package.json bebel 配置
"babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      "@babel/plugin-proposal-optional-chaining",
      "@babel/plugin-proposal-class-properties",
      [
        "import",
        {
          "libraryName": "antd-mobile",
          "style": "css"
        }
      ]
    ]
}

preview.js

可以设置全局默认的 parameter、decorator、loader、全局变量 等等

// 设置默认的主题
export const parameters = {
  docs: {
    theme: themes.light
  }
}

preview-head.html

用于在 preview 页面头部追加一些通用的内容

// 设置 rem 默认字体大小,重置 sb 的一些样式
<style>
  html {
    font-size: 26.6667vw;
  }

  /* reset story book style */
  .sb-show-main.sb-main-padded {
    padding: 16px;
  }

  .sbdocs-wrapper {
    padding-top: 64px !important;
    padding-bottom: 64px !important;
  }
</style>

名词

  • story
  • component
  • CSF
  • Args
  • Parameters
  • Decorators
  • Loaders
  • DocsPage
  • MDX
  • Controls
  • Actions

story

story 是描述如何渲染一个组件的函数,可以说是文档的最小结构

component

同一个组件的不同状态渲染(story),都包含在一个 componet 下

Args

定义 story 的参数,Story Args and Component Args

Parameters

关于 story 静态的命名的元数据,用来控制功能和插件,eg:parameters.backgrounds 用来控制工具栏插件的背景颜色

Decorators

可以在 story 外面包裹自定义的 dom 结构,方便定义 story 渲染

Loaders

Loaders 是一个用来给 story 加载数据的异步函数,比如从远程接口获取 story 渲染需要的数据

DocsPage

自动生成包含所有 stories 文档,基于 addon-docs 插件

CSF

Component Story Format 缩写,是指用 js 形式来定义 story 的语法格式

MDX

新的文件格式,包含 MarkDown 和 JSX,

Controls

图形化界面用来控制 story 渲染的状态,通过 Args 和 ArgsType 定义

Actions

用来显示 story 事件回调接收的数据

如何写

// FloatBoard.stories.js

import React from 'react'
import FloatBoard from './float_board'

// component parameter 组件的描述和属性
export default {
    title: 'FloatBoard'
    component: FloatBoard
}

// story
export const FB1 = (args) => <FloatBoard {...args} />

// actions
FB1.args = {
    primary: 1
}
FB1.argTypes = {
    type: {
        otions: ['primary', 'secondary'],
        control: 'select'
    }
}
// FloatBoard.stories.mdx

import React from 'react'
import { Meta, Canvas, Story} from '@storybook/docs'
import FloatBoard from './float_board'

// component parameter 组件的描述和属性
<Meta
    title='FloatBoard'
    component={FloatBoard}
/>

// story
<Canvas>
    <Story name="Base FloatBoard">
        <FloatBoard ... />
    </Story>
</Canvas>

MDX 的示例

import { Meta, Story, ArgsTable } from "@storybook/addon-docs"
import FloatBoard from "./index"
import Preview from "mt-docs/preview"
import { utils } from "common"
const floatData = {
    bg_url: "http://f.mengtuiapp.com/common/share_150x150.png",
    click_type: 0,
    deadline: Date.now()/1000 + 12*60*60,
    h5_popup: true,
    link: "xxx",
    on: true,
    position: 0,
    scale: 0,
    subtitle: "subtitle",
    ticktock: 2,
    ticktock_bg_url: "http://f.mengtuiapp.com/common/share_150x150.png",
    title: "title",
    toast: "toast",
    // swagger里未找到time的定义,无此参数倒计时不会显示
    time: 0,
}
const scope = { floatData, FloatBoard }
const code = `
  <FloatBoard
      floatBoard={floatData}
  />
`

<Meta title="FloatBoard" component={FloatBoard} />

# FloatBoard

优惠券悬浮窗

## 基本功能

<Story name="基础用法" args={{floatBoard: floatData}}>
  {(args) => <FloatBoard {...args} />}
</Story>

## Props

<ArgsTable story="基础用法" />

CSF 的示例

import React from 'react'
import FloatBoard from './index'

const floatData = {
  bg_url: "http://f.mengtuiapp.com/common/share_150x150.png",
  click_type: 0,
  deadline: Date.now()/1000 + 12*60*60,
  h5_popup: true,
  link: "xxx",
  on: true,
  position: 0,
  scale: 0,
  subtitle: "subtitle",
  ticktock: 2,
  ticktock_bg_url: "http://f.mengtuiapp.com/common/share_150x150.png",
  title: "title",
  toast: "toast",
}

export default {
  title: 'FloatBoard',
  component: FloatBoard
}

export const MyFloatBoard = (args) => <FloatBoard {...args} />

MyFloatBoard.args = {
  floatData
}

ArgsTable

归属于 addon-docs 插件,用来展示 ArgTypes 定义的属性列表,表格形式展示。使用 addon-docs 插件会默认给每个 sotry 生成一组 ArgTypes,它是从组件定义的 propTypes 和 defaultProps 获取,不论类组件、函数组件、高阶组件都可以生成。

用法

// MDX 形式

// 直接用于组件上,显示指定组件的属性
import ComponentA from 'xxx'
<ArgsTable of={ComponentA} />


// 用于 story 上,显示对应组件的属性
<Story name="mystory">
    <ComponentA />
</Story>
<ArgsTable story="mysotry" />

'loose' mode configuration must be the same...

Error: .storybook/preview.js-generated-config-entry.js: 'loose' mode configuration must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled).

遇到的问题是在一个已有的 bebel 配置的项目加入 storybook,项目原有的 babel 配置和 storybook 默认的不一致,比如 plugin-proposal-class-properties 插件,它默认 option.loose=false,和默认 sb 的 babel 配置不一致。

需要确保 storybook 默认的 babel 配置和项目的是一致,修改 sb 的 babel 配置(plugins & presets) loose=false 即可。

// 原项目默认 babel plugin 配置
[
  "@babel/plugin-proposal-optional-chaining",
  "@babel/plugin-proposal-class-properties",
  [
    "import",
    {
      "libraryName": "antd-mobile",
      "style": "css"
    }
  ]
]
// sb 默认的 babel preset 配置
[
  [
    '@storybook/core-common/node_modules/@babel/preset-env',
    { shippedProposals: true, loose: true }
  ],
  '@storybook/core-common/node_modules/@babel/preset-typescript',
  [
    '@storybook/react/node_modules/@babel/preset-react',
    {}
  ],
  '@babel/preset-flow'
]

// sb 默认的 babel plugin 配置

[
  '@babel/plugin-transform-shorthand-properties',
  '@babel/plugin-transform-block-scoping',
  [
    '@babel/plugin-proposal-decorators',
    { legacy: true }
  ],
  [
    '@babel/plugin-proposal-class-properties',
    { loose: true }
  ],
  [
    '@babel/plugin-proposal-private-methods',
    { loose: true }
  ],
  '@babel/plugin-proposal-export-default-from',
  '@babel/plugin-syntax-dynamic-import',
  [
    '@babel/plugin-proposal-object-rest-spread',
    { loose: true, useBuiltIns: true }
  ],
  '@babel/plugin-transform-classes',
  '@babel/plugin-transform-arrow-functions',
  '@babel/plugin-transform-parameters',
  '@babel/plugin-transform-destructuring',
  '@babel/plugin-transform-spread',
  '@babel/plugin-transform-for-of',
  'babel-plugin-macros/dist/index.js',
  '@babel/plugin-proposal-optional-chaining',
  '@babel/plugin-proposal-nullish-coalescing-operator',
  [
    'babel-plugin-polyfill-corejs3',
    {
      method: 'usage-global',
      absoluteImports: 'core-js',
      version: '3.18.3'
    }
  ],
  'babel-plugin-add-react-displayname'
]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容