各位靓仔靓女们,大家好!今天咱们不聊风花雪月,就来唠唠嗑,关于如何给Vue组件库打造一个集美貌与智慧于一身的交互式文档和测试平台。就拿Storybook来说吧,这玩意儿简直就是组件的专属“展示厅+游乐场”,能让你的组件亮瞎别人的眼,还能让开发测试过程变得像玩游戏一样轻松愉快。
第一幕:Storybook“粉墨登场”——安装与配置
好,闲话少说,咱们先让Storybook登场。安装过程嘛,就像装软件一样简单粗暴,一条命令搞定:
npx storybook init
这条命令会分析你的项目,自动安装相应的依赖,并创建一个.storybook
的文件夹,里面住着Storybook的配置文件。如果你的项目比较特殊,或者你想手动配置,可以这样:
-
安装依赖:
npm install -D @storybook/vue3 @storybook/addon-essentials @storybook/addon-links @storybook/addon-interactions @storybook/testing-library
@storybook/vue3
: Storybook对Vue 3的支持。如果是Vue 2,请换成@storybook/vue
。@storybook/addon-essentials
: 一些常用的插件,比如控制面板、文档生成等。@storybook/addon-links
: 允许你在 Storybook 故事之间创建链接。@storybook/addon-interactions
: 允许你创建可交互的故事,并使用 Play 函数进行测试。@storybook/testing-library
: 方便你在 Play 函数中使用 Testing Library 进行断言。
-
配置
.storybook/main.js
:module.exports = { stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ '@storybook/addon-essentials', '@storybook/addon-links', '@storybook/addon-interactions', ], framework: { name: '@storybook/vue3', options: {}, }, docs: { autodocs: true, }, };
stories
: 定义Storybook去哪里找你的故事文件(后面会讲到)。addons
: 引入你需要的插件。framework
: 声明你使用的框架。docs
: 启用自动文档生成。
-
配置
.storybook/preview.js
(可选):import '../src/assets/main.css'; // 引入你的全局样式(如果需要) export const parameters = { actions: { argTypesRegex: "^on[A-Z].*" }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, };
- 这里可以配置一些全局的参数,比如事件处理函数的命名规则、控制器的匹配规则等。你还可以在这里引入你的全局样式、注册全局组件等等。
第二幕:编写你的第一个故事——组件的“个人秀”
好,有了舞台,接下来就要让你的组件登台表演了。Storybook里,每个组件的“表演剧本”就叫做“故事”(Story)。咱们来创建一个简单的按钮组件的故事:
-
创建
src/components/MyButton.vue
:<template> <button :class="classes" @click="$emit('click')"> {{ label }} </button> </template> <script> export default { props: { label: { type: String, default: 'Button', }, primary: { type: Boolean, default: false, }, size: { type: String, default: 'medium', validator: (value) => ['small', 'medium', 'large'].includes(value), }, }, computed: { classes() { return [ 'my-button', `my-button--${this.size}`, { 'my-button--primary': this.primary }, ]; }, }, emits: ['click'], }; </script> <style scoped> .my-button { border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; } .my-button--primary { background-color: #007bff; color: white; } .my-button--small { padding: 5px 10px; font-size: 0.8em; } .my-button--medium { font-size: 1em; } .my-button--large { padding: 15px 30px; font-size: 1.2em; } </style>
-
创建
src/components/MyButton.stories.js
:import MyButton from './MyButton.vue'; export default { title: 'Components/MyButton', // Storybook里的分类 component: MyButton, argTypes: { label: { control: 'text', description: '按钮的文本' }, primary: { control: 'boolean', description: '是否是主要按钮' }, size: { control: 'select', options: ['small', 'medium', 'large'], description: '按钮的大小' }, onClick: { action: 'clicked', description: '按钮点击事件' }, }, }; const Template = (args) => ({ components: { MyButton }, setup() { return { args }; }, template: '<MyButton v-bind="args" @click="args.onClick" />', }); export const Primary = Template.bind({}); Primary.args = { primary: true, label: 'Primary Button', }; Primary.parameters = { docs: { source: { code: '<MyButton primary label="Primary Button" />', }, }, }; export const Secondary = Template.bind({}); Secondary.args = { label: 'Secondary Button', }; export const Small = Template.bind({}); Small.args = { size: 'small', label: 'Small Button', }; export const Large = Template.bind({}); Large.args = { size: 'large', label: 'Large Button', };
title
: 定义这个故事在Storybook里的分类,方便你组织组件。component
: 指定这个故事是关于哪个组件的。argTypes
: 定义组件的属性(props),以及它们在Storybook里的控制方式。control
指定了控制器的类型,options
指定了可选值,description
添加了属性的描述,action
可以让Storybook监听事件,并在界面上显示事件触发的信息。Template
: 一个函数,用于渲染组件。它接收args
作为参数,args
包含了你配置的属性值。Primary
,Secondary
,Small
,Large
: 不同的故事,代表组件的不同状态。args
: 每个故事的属性值。parameters
: 可以为每个 story 单独设置参数,比如自定义文档源码。
第三幕:启动Storybook——“验收成果”的时刻
一切就绪,是时候启动Storybook,看看我们的“作品”了:
npm run storybook
或者
yarn storybook
打开浏览器,访问Storybook的地址(通常是http://localhost:6006
),你就能看到你的组件在Storybook里闪耀登场了!你可以在控制面板里调整属性值,实时看到组件的变化,还可以点击按钮,看到事件触发的信息。
第四幕:高级技巧——让你的Storybook更上一层楼
-
使用MDX编写文档:
除了用JavaScript编写故事,你还可以用MDX(Markdown + JSX)来编写更丰富的文档。MDX允许你在Markdown文档里嵌入JSX代码,让你可以用更灵活的方式展示组件的用法和示例。
{/* src/components/MyButton.stories.mdx */} import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs'; import MyButton from './MyButton.vue'; <Meta title="Components/MyButton" component={MyButton} /> # MyButton 这是一个按钮组件,可以自定义文本、大小和颜色。 <Canvas> <Story name="Primary"> <MyButton primary label="Primary Button" /> </Story> </Canvas> <ArgsTable of={MyButton} />
Meta
: 定义故事的元数据,比如标题和组件。Story
: 定义一个故事。Canvas
: 创建一个画布,用于渲染故事。ArgsTable
: 自动生成属性表格。
-
使用
play
函数进行交互测试:@storybook/addon-interactions
允许你编写play
函数,模拟用户与组件的交互,并使用 Testing Library 进行断言。import MyButton from './MyButton.vue'; import { fireEvent, within } from '@storybook/testing-library'; import { expect } from '@storybook/jest'; export default { title: 'Components/MyButton', component: MyButton, argTypes: { onClick: { action: 'clicked' }, }, }; const Template = (args) => ({ components: { MyButton }, setup() { return { args }; }, template: '<MyButton v-bind="args" @click="args.onClick" />', }); export const Interaction = Template.bind({}); Interaction.args = { label: 'Click Me', }; Interaction.play = async ({ canvasElement }) => { const canvas = within(canvasElement); const button = await canvas.getByRole('button', { name: 'Click Me' }); await fireEvent.click(button); await expect(button).toBeInTheDocument(); // 一个简单的断言 };
play
: 一个异步函数,它接收一个对象作为参数,这个对象包含了当前故事的上下文信息,比如画布元素。within
: 一个函数,它可以让你在指定的元素范围内查找元素。fireEvent
: 一个函数,它可以让你模拟用户与元素的交互,比如点击、输入等。expect
: 一个函数,它可以让你进行断言,验证组件的行为是否符合预期。
-
使用 ThemeProvider 集成主题:
如果你希望 Storybook 中的组件展示不同的主题,你可以使用 Vue 的
provide/inject
特性,结合 Storybook 的decorators
来实现。{/* src/components/ThemeProvider.vue */} <template> <slot /> </template> <script> import { provide } from 'vue'; export default { props: { theme: { type: Object, required: true, }, }, setup(props) { provide('theme', props.theme); return {}; }, }; </script>
{/* src/components/MyComponent.vue */} <template> <div :style="themedStyles"> {{ message }} </div> </template> <script> import { inject, computed } from 'vue'; export default { props: { message: { type: String, default: 'Hello World', }, }, setup() { const theme = inject('theme'); const themedStyles = computed(() => ({ backgroundColor: theme.value.background, color: theme.value.text, padding: '10px', })); return { themedStyles }; }, }; </script>
{/* src/components/MyComponent.stories.js */} import MyComponent from './MyComponent.vue'; import ThemeProvider from './ThemeProvider.vue'; export default { title: 'Components/MyComponent', component: MyComponent, decorators: [ (story, context) => { const theme = { background: context.globals.backgrounds?.value || 'white', text: context.globals.textColor?.value || 'black', }; return { components: { ThemeProvider, story }, template: `<ThemeProvider :theme="theme"><story /></ThemeProvider>`, }; }, ], parameters: { backgrounds: { values: [ { name: 'light', value: 'white' }, { name: 'dark', value: 'black' }, ], }, textColor: { values: [ { name: 'light', value: 'black' }, { name: 'dark', value: 'white' }, ], }, }, globals: { backgrounds: { value: 'light' }, textColor: { value: 'dark' }, }, }; const Template = (args) => ({ components: { MyComponent }, setup() { return { args }; }, template: '<MyComponent v-bind="args" />', }); export const Default = Template.bind({}); Default.args = { message: 'Themed Component', };
在这个例子中,
ThemeProvider
组件接收一个theme
prop,并通过provide
将其注入到组件树中。MyComponent
组件通过inject
接收theme
,并根据主题动态地计算样式。在 Storybook 中,我们使用decorators
将ThemeProvider
包裹在每个故事周围,并根据 Storybook 的全局参数动态地设置主题。
第五幕:组件库文档和测试平台的终极目标
功能 | 描述 |
---|---|
组件展示 | 清晰地展示组件的各种状态和用法,包括属性、事件、插槽等。 |
交互式演示 | 允许用户在浏览器中实时调整组件的属性,查看组件的变化,模拟用户的交互行为。 |
自动生成文档 | 根据组件的源代码和故事文件,自动生成API文档,包括属性的类型、默认值、描述等。 |
自动化测试 | 通过编写 play 函数,模拟用户与组件的交互,并使用 Testing Library 进行断言,验证组件的行为是否符合预期。 |
主题定制 | 允许用户自定义 Storybook 的主题,使其与组件库的风格保持一致。 |
版本控制 | 将 Storybook 集成到版本控制系统中,方便团队协作和版本管理。 |
部署与分享 | 将 Storybook 部署到服务器上,方便团队成员和用户访问。 |
总结
有了Storybook,你的Vue组件库就能拥有一个强大的“大脑”和“心脏”,不仅能让组件的开发和测试效率大大提高,还能让你的组件库更容易被理解和使用。所以,赶紧行动起来,让你的组件库也拥有一个属于自己的Storybook吧!
好了,今天的“讲座”就到这里,希望对大家有所帮助!记住,编程的乐趣在于不断学习和尝试,遇到问题不要怕,大胆地去探索,你会发现编程的世界充满了惊喜!下次有机会再和大家分享其他的技术心得,拜拜!