在使用现代JavaScript框架的过程中,你很快就会意识到,用户界面正在变成组件驱动的。将界面分割成组件可以提供更好的代码重用,有利于DRY软件原则,并改善有类似需求的项目的代码共享。
这种由框架带来的方法现在已经成为前端开发的支柱,使web开发整体上更加平易近人,不至于让人不知所措。现在我们有强大的框架,如Vue.js、React、Svelte和SolidJS,所有这些都是在开发体验上的巨大改进,为开发者提供了许多有用的工具来构建这些组件。
像Vue这样的框架让我们有能力在SFC(单文件组件)中编写可重用的代码,并有机会通过道具、事件和代码注释来记录它。然而,在开发过程中,我们可能仍然觉得缺乏一种更方便的方式来为我们的组件提供坚实的文档。
这就是Storybook出现的地方。Storybook提出了一种关于可视化测试和组件隔离的可靠方法。在处理大量复杂的组件时,或者在与用户界面有更多互动的大型团队中,它可能会派上用场。
在这篇文章中,我们将对Storybook进行实际介绍。我们将讨论它是如何与Vue互动的,浏览一下我们在用Storybook设置Vue项目时可能遇到的瓶颈,并学习如何克服它们。
什么是Storybook?
Storybook是一个开源的工具,它可以帮助你隔离你的组件,同时赋予你 "玩 "它们的能力。
在构建你的界面组件时,你可能需要对它们进行沙盒处理,以方便测试、预览和记录。Storybook将提高你的整体代码的可维护性,使你的组件像独立的实体一样显示出来,可以随时测试并与其他组件集成。
此外,你的沙盒组件可以通过你托管的Storybook实例与你团队中的任何人分享,这可以帮助你与参与构建界面的其他参与者建立更顺畅的沟通。
用Vue 3设置Storybook
我们将用几个组件创建一个基本的单页应用程序,然后在Storybook上设置我们的故事,以便与它们一起工作。
一个故事可以捕捉到一个UI组件的渲染状态。在实践中,一个故事只是在一个带有预览面板和操作按钮的环境中记录了你的孤立组件的形式,这将帮助你调整和可视化你的组件的状态。
在开始Storybook之前,你需要让你的Vue应用程序运行。在本教程中,我使用的是Vite,一个为你的Web应用提供更好开发体验的构建工具。
运行以下命令,让你的Vue项目运行。
npm init vite@latest
现在你有一个现有的Vue应用程序(Storybook更容易安装在现有的应用程序中,因为它有自动检测项目的能力),一旦你运行这个命令,初始化Storybook将根据你的项目文件创建样本文件。
npx sb init
上面的命令将安装Storybook及其依赖项,配置Storybook的实例,并添加一些模板代码来指导你。
在你的src/stories 目录中,你会发现由Storybook添加的代码。故事是以xxx.stories.js 为扩展名的文件;其他文件是你项目文件的样本。它们将被删除,因为它们不是你的应用程序的组件。Storybook提供它们作为指导如何为你的Vue组件编写故事的手段。
现在你已经熟悉了Storybook的初始化阶段,你可以启动你的应用程序,以确保一切运行良好。
npm run storybook
它应该为Storybook启动一个本地开发服务器,并自动在一个新的浏览器标签中打开。
这是运行上述命令时打开的介绍页面,它与Storybook创建的introduction.stories.mdx 文件相对应。这个文件是用MDX编写的,MDX是一种能够理解JSX的标记语言。
MDX中的文件被看作是文档故事,因为它们没有隔离组件进行测试,也没有提供一个游乐场。相反,它们可以被用于文档页面、介绍页面或关于页面。
在你的Vue.js应用程序中使用Storybook
现在我们有了一个工作的应用程序和一个正在运行的Storybook实例,我们将用一些基本的按钮、卡片和文本创建一个简单的应用程序,然后我们将创建它们的故事,调整它们的状态并设置基本的自动化视觉测试。
我已经创建了一些组件,你可以在你的Vue项目中的src/components 。
第一个是一个按钮组件。
// Button.vue
<script setup lang="ts">
import Card from './components/Card.vue'
import Button from './components/Button.vue'
</script>
<template>
<Card title="Hello"></Card>
<Button label="Hello"></Button>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
第二个是卡片组件。
// Card.vue
<template>
<div class="card" @click="onClick" :disabled="disabled" :style="style">
{{ title }}
</div>
</template>
<script lang="ts">
import { reactive, computed } from 'vue';
export default {
name: 'card',
props: {
title: {
type: String,
default: 'Button',
required: true,
},
disabled: {
type: Boolean,
default: false,
},
rounded: {
type: Boolean,
default: false,
},
backgroundColor: {
type: String,
default: '#efcccf',
},
},
emits: ['click'],
setup(props: any, { emit }: any) {
props = reactive(props);
return {
style: computed(() => ({
backgroundColor: props.backgroundColor,
borderRadius: props.rounded ? '3em' : '0px',
})),
onClick() {
emit('clicked');
}
}
},
};
</script>
<style scoped>
.card {
font-family: Helvetica, Arial, sans-serif;
border: 0;
text-align: left;
padding: 15px;
width: 200px;
height: 100px;
box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px;
}
</style>
最后,第三个是一个文本组件。
// Text.vue
<template>
<p :style="style">
{{ label }}
</p>
</template>
<script lang="ts">
import { reactive, computed } from 'vue';
export default {
name: 'txt',
props: {
label: {
type: String,
default: 'Button',
required: true,
},
size: {
type: Number,
default: 14,
},
},
setup(props: any) {
props = reactive(props);
return {
style: computed(() => ({
fontSize: `${props.size}px`,
}))
}
},
};
</script>
创建你的组件的故事
我们已经创建了我们的三个组件;现在让我们创建它们各自的故事,看看会发生什么。

我们通过编写与组件相对应的不同故事来创建上面的预览。为了使文章不那么累赘,我将只展示按钮故事的代码。
// button.stories.js
import Button from '../components/Button.vue';
export default {
title: 'Button',
component: {Button},
argTypes: {
label: 'String',
backgroundColor: { control: 'color' },
},
};
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { Button },
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
return { args };
},
// And then the `args` are bound to your component with `v-bind="args"`
template: '<Button label="hello" v-bind="args" />',
});
export const Rounded = Template.bind({});
Rounded.args = {
label: 'Button',
rounded: true,
};
export const Normal = Template.bind({});
Normal.args = {
label: 'Button',
};
如果你想看其他两个组件的代码,这里有我GitHub上的完整源代码。
你的大多数故事都会有上述配置。它们被称为组件故事格式(CSF)。组件故事格式是一个基于JavaScript ES6模块的组件实例的开放标准。这使得开发、测试和设计工具之间能够相互合作。它在UXPin和WebComponents中也能看到。
在Storybook中,我们可以将一个故事的代码分成默认导出部分、模板部分和名称导出。
默认导出
默认导出包含代表组件元数据的一切,包括组件本身。标题应该是唯一的,并接受类似路径的字符串,以防你想把你的故事组织在文件夹里(例如base/button )。
你可以包括参数类型(argTypes )和参数,以改善用户在调整状态时的体验。它们占了故事预览底部的行动按钮的位置。例如,在我们创建的按钮故事中,我们将backgroundColor 与一个颜色选择器相关联。我们没有为Vue组件中的其他道具添加argTypes ,因为Storybook能够理解原始类型并自动创建相应的动作工具。
下面是Storybook在故事中读取你的默认输出后的渲染效果。它能够从你的组件中获取道具(rounded: Boolean 和label: String ),并为它们创建适当的控制输入。然后,你可以指定一个自定义的参数类型,就像我们为背景颜色所做的那样,添加一个颜色选择器输入。

模板部分
模板部分帮助Storybook用必要的道具和绑定来调用你的Vue组件。然后这些绑定被共享给命名的出口,以创建不同的故事设置,如上图所示。
命名的出口
命名的输出是你的应用程序的不同故事。它们对应于用特定配置渲染你的应用程序的一种方式。
你可以依靠参数,仍然能够沙盒和测试你的组件,然而,命名导出的方法帮助你为你的组件创建一个特定的设置,以便在故事中显示。
你可以在官方文档中找到更多关于如何编写故事的信息。
额外提示
附加组件
Storybook为现代前端开发提供了一种特殊的方法。由于它的灵活性,我们有能力使我们的Storybook实例适合我们的使用情况,甚至使我们的项目更容易维护。Storybook展示了一系列的 "附加组件",它们可以帮助你实现高度个性化的故事设置。
如果你需要Vue中的路由故事,用于依赖路由器状态的组件,你可能会从Vue 3 storybook路由器插件中受益。
用故事书测试
storyshots插件将帮助你用Jest自动创建代码快照。这些快照可以帮助你测试每个组件的视觉回归。它们甚至可以被集成到你的CICD过程中,在你的项目升级之间为你提供防错的代码。
测试插件可以帮助你为你的故事编写单元测试(用Jest),而不用离开Storybook。这些测试为你的组件提供了双层的稳健性,因为它们是在Storybook的沙盒中测试的。
还有更多...
Storybook可以做到模拟数据,用Figma或Zeplin等工具整合设计洞察力,用工具栏和控件直观地调试你的代码,以及测试国际化。
注意事项
尽管Storybook很有帮助,但要使它完全按照你的要求工作,也是很麻烦的。下面列出了你可能遇到的常见障碍,以及克服这些障碍的技巧。
我的CSS不适用
如果你使用的是基本的CSS,你应该检查它是否有范围。Vue中的范围化样式只适用于它们被声明和范围化的那个组件。
因为Storybook隔离了你的组件,你不应该期望全局样式被添加到这些组件中。
将你的CSS范围化(建议这样做)或通过导入'../styles/globals.css' ,在你的.storybook/preview.js 文件中导入全局样式。
如果你使用SASS或任何CSS预处理器,你需要更新你的.storybook/main.js 文件,通过扩展一个WebPack加载器来帮助它处理SCSS。
有时你的字体没有被导入,这些字体也需要被加载。你必须创建一个.storybook/preview-head.html 文件并像在HTML文件中那样调用你的字体。
我不能在我的故事中使用某种框架
如果你正在使用Vuetify、Naive UI、Chakra或任何其他Vue UI框架,你可能会看到它们没有被应用到你的组件上。这是因为Storybook本身被认为是另一个实例,它只通过它沙盒中的组件与你的应用程序有联系。
为了解决这个问题,你需要用框架的包装器来装饰你的故事(如果它有的话),或者像你通常在你的应用程序中那样导入它,使其发挥作用。
Storybook是一个流行的、维护良好的软件。如果你查看他们的文档或讨论区,你在工作中可能遇到的大多数限制都已经解决了,或者有一个现有的变通方案。
总结
像Vue这样伟大的框架总是有一个坚实的生态系统作为后盾,帮助你快速获得完美的代码。Storybook作为这个生态系统的一个重要组成部分,为开发者提供了一个不可替代的文档和测试工具。
如果你有兴趣,你可以在这个CodeSandbox中找到这篇文章的代码。
The postGetting started with Storybook in Vue 3appeared first onLogRocket Blog.