前言
前段时间要搭建一个业务组件的管理平台,类似于 element-ui 的官方文档,对比了几个业界使用比较广泛的工具(StoryBook,Styleguidist,Docz)最终选择了 StoryBook,于是就有了本文。
本文将介绍 storybook + vue2.x + element-ui 构建业务组件管理平台入门内容。其中更多的是作了一些个人项目需要的点的描述以供参考,想要全面了解 Storybook,建议阅读官网文档。
对比一般 vuepress 生成的组件 doc,storybook 构建的组件doc 有个很好的点是可以根据组件 props 的注释、type 和默认值生成一个 props 说明表,这个表中有个 control 列,可以即时控制属性与组件的交互。
如图:
在线预览: storybook-vue-element
github地址:github.com/lbj-git-sz/…
一、初始化
可以新建一个vue项目然后运行以下命令,也可以在现有的项目上直接运行以下命令,本人选择的后者,下文也以这种方式作为描述的基础。
npx sb init
正常执行上述命令之后会在项目根目录中出现.storybook文件夹:
├── storybook
│ ├── main.js # story、插件、webpack等配置文件
│ └── preview.js # 界面视图、样式等配置文件
同时,会在 src 目录下创建 .stories 目录, 为了与业务代码做目录区分, 可以将 .stories 目录迁移到其他地方,只需要在 .storybook/main.js 中修改路径即可:
"stories": [
"../storybook/**/*.stories.mdx",
"../storybook/**/*.stories.@(js|jsx|ts|tsx)"
]
运行 npm run storybook 命令即可启动 Storybook 服务,其实在执行完 npx sb init 时已经在 .stories 文件夹中创建了几个例子 story,页面如下:
二、编写基于element-ui开发的业务组件的stories
(举例业务组件ScSelect)
2.1 在.stories中创建ScSelect.storise.js文件
2.1.1 组成
storybook组件包括两个基本部分:基本模板与各个story,story的本质是不同状态的基础模板。下面是基础模板:
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
import ScSelect from '@/components/ScSelect';
export default {
title: 'ScSelect',
component: ScSelect,
// More argTypes: https://storybook.js.org/docs/vue/api/argtypes
// 通常用在想要指定值域的场景,比如有限数据的select
argTypes: {
showHeader: {
defaultValue: true,
control: { type: 'boolean' },
description: '控制头部展示与否'
},
showToggleBtn: {
defaultValue: true,
control: { type: 'boolean' },
description: '控制toggle按钮显示与隐藏'
},
// 在此配置的args会作用到所有stories中,就是全局args
args: {
}
},
};
// More on component templates: https://storybook.js.org/docs/vue/writing-stories/introduction#using-args
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { ScSelect },
template: '<ScSelect v-bind="$props" />',
});
2.1.1.1 argTypes
ArgTypes 是 Storybook 中用于指定Args行为的特性。 通过指定一个参数的类型或者提供默认值,可以在渲染出来的组件中 control 其行为属性。argTypes 通常有两种呈现形式:
-
自动推断
Storybook 在开发者构建 .stories.js 文件并写完基础模板时就能根据原来组件里写属性已经注释自动推断出来,如:
props: {
/** 是否设置为自动匹配 */
filterable: {
type: Boolean,
default: true
},
/** 数据双向绑定 */
modelVal: {
type: [String, Number],
default: null
},
/** 请求下拉列表的接口url,当传入resData时不会发送接口请求(故此时不需传入url) */
url: {
type: String,
default: ''
},
/** 是否设置为不可编辑 */
disabled: {
type: Boolean,
default: false
},
/** 是否设置为可一键清楚内容 */
clearable: {
type: Boolean,
default: true
},
/** v-for循环设置的key */
forKey: {
type: String,
default: ''
},
/** 下拉选项中的value对应的key */
valKey: {
type: String,
default: 'value'
},
/** 下拉选项中的label对应的key */
labelKey: {
type: String,
default: 'label'
}
}
效果如下图:
注释
自动推断是通过原组件中的注释来生成上图中 description 列的文案,具体怎么设置注释参考 vue + storybook 注释说明,如slots的description
-
手动描述
就是在 argTypes 属性中添加想要描述的props(详见官网),如,有限枚举下拉选择的 control 设置默认值等。
export default {
title: 'Example/Button',
component: Button,
argTypes: {
type: {
control: { type: 'select' },
defaultValue: 'primary',
options: ['primary', 'warning', 'info'],
},
},
}
2.1.2 提供上下文
引入 ScSelect 组件时 Storybook 并不会逐层往上溯源, 比如要想 ScSelect 组件在 Storybook 工程中正常运行,还需手动引入elment-ui(ps: 可以按需引入),也就是说在 Storybook 中渲染一个组件需要为其提供其所需的上下文。
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
import ScSelect from '@/components/ScSelect';
2.1.3 样式预处理器配置
Storybook 默认是不能编译 scss 编码的,需要在 .storybook/main.js 配置一下相关 loader
"webpackFinal": async (config, { configType }) => {
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
'@': path.join(__dirname, '../src'),
}
}
config.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
include: path.resolve(__dirname, '../'),
})
return config
}
2.1.4 公共样式引入
ScSelect 除了组件内局部样式外如果还有公共样式才能正确展示,可以在 .storybook/preview.js 中引入
import '@/styles/index.scss'
2.1.5 编写story
虽然说story本质上是不同状态下的组件,比如某些简单传入 args 的 story
// ScSelect.stories.js
export const 参数说明 = Template.bind({});
// More on args: https://storybook.js.org/docs/vue/writing-stories/args
参数说明.args = {
showToggleBtn: true
};
但是如果想要实现一些自己想要的复杂的交互效果,那么得将 story 当成一个完整的 vue 组件来写,比如做一个级联 story:
通过以上的操作可以初步实现一个业务组件的参数说明以及在线操作了,也还可以通过一下其他配置优化或者达到开发者想要的效果。
2.2 Parameter
Parameter 是用来定义 Stories 的静态 metadata,相当于对提供了配置参数给对应的addon(即 插件)进行相关的设定,例如用 Parameter 配置 toolbar 中的 background 项(就是渲染组件的容器的背景色)。
// Button.stories.js
Primary.parameters = {
backgrounds: {
values: [
{ name: 'story-red', value: 'pink' },
{ name: 'story-green', value: 'olive' },
],
},
};
ps: 后面会提到在 .storybook/preview.js中做全局的 parameter配置。
2.3 Decorators
Decorator 是一种设计模式,把要被装饰的 Story 当做参数,传入装饰器中,Decorator 可以帮 Story 加上额外的信息,赋予 Story 新的能力,而不需改动 Story 的本身, 如在 element-ui Select 组件默认在 Docs 中展示时是Docs宽度的100%,通过 Decorators 将 Select 组件限制为300px宽度:
// ScSelect.stories.js
export default {
title: 'ScSelect',
component: ScSelect,
decorators: [() => ({ template: '<div style="width: 300px"><story/></div>' })],
argTypes: {
}
};
-
sidebar
-
Tabs
Storybook 预设了两个tabs: Canvas和Docs,如果想隐藏或新增tab,可以在.storybook/manager.js中进行全局配置,也可以在story中的parametes中配置, 如
export const Defualt = Template.bind({});
// More on args: https://storybook.js.org/docs/vue/writing-stories/args
Defualt.args = {
showToggleBtn: true
};
Defualt.parameters = {
viewMode: 'docs', // 设置默认tab是docs
previewTabs: {
canvas: { // 隐藏canvas tab
hidden: true,
disable: true
}
}
}
-
toolbar
toolbar上的选项可以在每个 story 中局部设置,可以在 .storybook/preview.js 中进行全局配置, 如
/*
** 当组件样式为白底的时候将layout中的background设置为light比较合适,可以自定义, 如
** backgrounds: {
default: 'palette',
values: [
{
name: 'palette',
value: '#e5e5e5',
}
]
}
*/
backgrounds: {
default: 'light'
}
其他
-
actions -- 可以用于打印组件上的emit事件
安装插件命令:npm install -D @storybook/addon-actions//MyButton.vue <template> <div> <button @click="btnClick">{{ content }}</button> </div> </template> <script> export default { name: "MyButton", props: { content: String, }, methods: { btnClick() { this.$emit("btnClick", this.content); }, }, }; </script>//MyButton.stories.js import { action } from "@storybook/addon-actions"; import MyButton from "./MyButton.vue"; export default { title: "MyButton", component: MyButton, }; export const Actions插件 = () => ({ components: { MyButton, }, template: ` <my-button content="clickMe" @btnClick="btnClick"/> `, methods: { btnClick: action("btnClick"), }, });