Storybook 是当前最流行的 UI 组件开发环境,主要功能就是提供组件独立运行环境,方便编写组件使用样例,每个样例称之为一个 “story”。有了它,就可以方便的在项目中提取通用组件,管理自己的组件库。
并且,它还可以通过丰富的插件实现可交互测试,响应式布局,文档生成、设计稿预览等功能,让开发工作更加专注,和设计人员的协作更轻松。
安装 Storybook
Storybook 有很丰富的自定义配置项和大量的插件,可以适配各种开发场景,不过这也让项目的搭建变得很复杂,很新人劝退。🤣
好在最近更新的 6.0 版本之后,项目可以实现零配置搭建,就像 Spring Boot 对 Spring 的简化,让项目创建变得格外简单,现在一行命令就可以完成项目搭建。
Storybook 支持各种主流组件框架,这里以 Angular 来阐述。
创建 Angular 项目
首先默认全局已经安装了 Angular CLI:npm install -g @angular/cli
。顺便一提,Angular 11 大幅提升了构建速度和打包体积,没有破坏性改动,强烈安利。😋
在指定目录(如 D:\projects)下运行 ng new storybook-example
创建一个 Angular 项目,根据提示交互性的配置项目生成即可。
初始化 Storybook
接着在这个项目路径下(D:\projects\storybook-example)运行命令 npx sb init
即可完成 Storybook 的搭建。它会根据项目的依赖自动配置 Storybook。只需要这么一行命令,Storybook 就可以自动安装到项目中去。
安装完毕后,运行 npm run storybook
即可看到 storybook 成功运行了,so easy!😎
初始化做了什么?
虽然项目运行起来了,不过突然自动创建了一堆未知的文件,也让人心里没底,来看看项目初始化做了哪些事情吧。
📦 安装所需的依赖包:因为识别到当前目录下是一个 Angular 项目,所以安装的是 Angular 版本的 Storybook 依赖包。
- "@compodoc/compodoc": Angular 的组件文档生成工具。
- "@storybook/addon-actions": 用以记录事件触发的插件。
- "@storybook/addon-essentials": 官方维护的插件集合,带有默认配置。
- "@storybook/addon-links": 用以给组件 story 创建链接。
- "@storybook/angular": storybook 针对 Angular 的依赖。
🛠 设置 npm 脚本:
- "storybook": 本地运行 Storybook
- "build-storybook": 编译打包 Storybook 项目
🛠 在项目根目录创建 .storybook 文件夹,添加默认配置:
- main.js:项目的全局配置文件,定义了 story 的查找路径,以及引入的插件。
- preview.js:项目的渲染配置,包括全局样式的引入,通用性的变量等。
- tsconfig.json:自动识别项目采用 typescript 后自动生成的配置文件
- 📝 在 src/stories 目录下创建三个组件(button、header、page),以及它们的 story 示例
编写 stories
story 用于展示组件某个状态,每个组件可以包含任意多个 story,用来测试组件的各种场景。根据默认配置,只需要在组件的文件夹中,以 **.component.stories.ts 的格式创建即可。
story 语法
基本编写语法很简单,是 export
任意多个 function,每一个就是一个 story。导出主要分两种:
default export:默认导出,提供组件级别的配置信息,例如以下配置会注明组件的归类,并提供 Button 组件的信息,以便渲染这个组件。
// Button.stories.ts import Button from './button.component'; export default { title: 'Components/Button', component: Button, }
named export:命名导出,用以描述 story,如上所说,一个组件可以有若干个 story。
// Button.stories.ts // 创建一个模板,方便在后续的 story 中复用 const Template = (args: Button) => ({ props: args, }); export const Primary = Template.bind({}); // 复制 Template Primary.args = { background: '#ff0', label: 'Button' }; Primary.storyName = "主要状态" // 自定义 story 名 export const Secondary = Template.bind({}); Secondary.args = { ...Primary.args, label: '😄👍😍💯' }; // 复用上一个 story 的配置 export const Tertiary = Template.bind({}); Tertiary.args = { ...Primary.args, label: '📚📕📈🤓' } // 再来一个
通过复制模板 function,可以创建若干个 story,传入不同的参数,就可以分别渲染出组件的不同状态。每个 story 的名字默认是 function 名,也可以自定义。
Args(属性)
上一节看到了怎么去写一个 Story 文件,不过里面反复用到的 args 是什么呢?
它代表组件的输入属性(Angular 中的 @input(),Vue/React 中的 props),它有 2 个层级,方便灵活配置。
story 层级:
// Button.stories.ts const Template = (args: Button) => ({ props: args, }); // 在这个 story 中传入组件属性,只影响当前 story export const Primary = Template.bind({}); Primary.args = { primary: true, label: 'Primary', };
组件层级:
// Button.stories.ts import Button from './button.component'; // 组件层级(默认导出)中传入组件属性, // 这个 Button 组件的所有 stories 的 primary 属性都会是 true export default { title: "Button", component: Button, args: { primary: true, }, }
就像上一节所看到的,不同的 story 的 args 是可以复用的,这里用到了 ES6 的解构语法:
const Primary = ButtonStory.bind({});
Primary.args = {
primary: true,
label: 'Button',
}
// 复用 Primary story 的 args,并覆盖 primary 属性
const Secondary = ButtonStory.bind({});
Secondary.args = {
...Primary.args, // 合并上一个 args 对象
primary: false,
}
简单的导出几个 function,这个按钮组件的测试用例就写好了,看看效果
可以看到,这个按钮组件可以独立于项目运行了,并且右边工具栏可以自由更改它的属性,实时查看属性改变后的效果,还可以自动生成组件文档。
有 story 做示例,有实时的控制台,有文档,再也不怕写好的组件别人不知道怎么用了。😎
额外的配置项
除了写给组件写 story,很多时候也会需要配置插件,或者给组件提供额外的功能,这里看看是如何配置的吧。
Parameters(参数)
Parameters 用以配置 Storybook 和 插件,具有全局、组件、story 三个层级。
Story 拥有大量的插件,以下以简单的 backgrounds 插件举例,它用来控制组件容器的背景色,默认自带黑/白两色。
全局定义在根目录 .storybook/preview.js 下,会影响所有的 stories。这样配置后,每个 story 界面下都可以选择红/绿两色背景:
// .storybook/preview.js export const parameters = { backgrounds: { values: [ { name: 'red', value: '#f00' }, { name: 'green', value: '#0f0' }, ], }, };
组件层级下定义,会让这个组件的所有 stories 都可以选择指定的背景色
// Button.story.ts export default { title: 'Button', component: Button, parameters: { backgrounds: { values: [ { name: 'red', value: '#f00' }, { name: 'green', value: '#0f0' }, ], }, }, };
story 层级下的定义只会影响当前 story,其他 story 就只能选择默认的黑/白两色了。
// Button.story.ts export const Primary = Template.bind({}); Primary.args = { primary: true, label: 'Button', }; // 红绿背景只在这个 story 下可以选择 Primary.parameters = { backgrounds: { values: [ { name: 'red', value: '#f00' }, { name: 'green', value: '#0f0' }, ], }, };
Parameters 的配置是可以继承,同名的子级会覆盖父级的定义。
Decorators(装饰器)
每个 Decorator 也是 function,用来包裹 story,保持原有的 story 不变的情况下,额外给它添加额外的 DOM 元素、引入上下文环境、添加假数据等等。
和 Parameters 一样,它也可以定义在全局/组件/story 三个层级,每个 Decorator 按定义顺序依次执行,从全局到 story。
例如,用一个额外的 <div>
包裹每个 story 的组件渲染:
// button.stories.ts
import { Meta, Story } from '@storybook/angular';
import { ListComponent } from './list.component';
export default {
title: 'Example/List',
component: ListComponent,
decorators: [
(storyFunc) => {
const story = storyFunc();
return {
...story,
template: `<div style="height: 60px">${story.template}</div>`,
};
}
]
} as Meta;
这样一来,这个列表组件的所有 story,都会展示出它在一个 60 像素高的容器内的呈现效果。
除了给组件包裹额外的元素,再例如为复合组件添加组件依赖:
// List.stories.ts
import { moduleMetadata } from '@storybook/angular';
import { CommonModule } from '@angular/common';
import List from './list.component';
import ListItem from './list-item.component'
// 给 list 组件添加它需要的组件和模块依赖
export default {
title: 'List',
component: List,
decorators: [
moduleMetadata({
declarations: [ListItem],
imports: [CommonModule],
}),
],
};
const Template = (args: List) => ({
component: List,
props: args,
});
就像平常需要在 ngModule 中声明似的,moduleMetadata
装饰器可以轻松测试各种组件,让你能在 Storybook 中从小到大搭建组件库。
组件驱动开发
现代用户界面愈加复杂,在日新月异的技术发展下,人们愈加期望独特,个性的用户体验。但这也意味着前端和设计需要给 UI 界面嵌入更多的逻辑和设计,这让前端变得原来越重,界面也越加复杂难以维护和测试。
将 UI 模块化分离也成了趋势,分离页面逻辑到一个个独立的交互模块中,将复杂的业务拆分解构,每个模块易于测试,组合和复用。所以现在组件化框架成了绝对的主流。相似的,当前微服务和容器化也变得非常流行。
开发流程
那么组件驱动的开发又是如何呢?
- 一次编写一个组件:首先编写一个个独立组件,定义它们的输入属性和输出事件,始于微末(例如 Avatar、Button、Input、Tooltip)。没错,这就是咱们熟悉的一个个组件库所做的事情:Material、Antd、ElementUI……
- 组合组件:构成更复杂的业务模块,构成新的功能(例如 Forms、Header、List、Table)。
- 装配页面:将业务模块构建出页面,使用假数据来模拟页面的各种使用场景和边缘用例。(例如首页、设置页、个人主页)
- 集成页面到应用:将页面与后台数据接口,业务逻辑服务层对接,构成实际应用程序(例如 xx监管系统、xxx商城、xx官网)
所以 Storybook 初始化项目后,创建了 button、header、page 三个组件示例,它们就代表了这样的开发阶梯流程。
适用场景
没有万精油的开发模式,总是需要按需选择技术栈和开发模式的,我们重要去了解每个优劣场景,这个也不例外。
优势;
- 高质量: 通过编写各种 stories 来验证组件的运行场景,测试用例越多,组件质量越发可靠和健壮。
- 易测试: 在组件级别进行测试,将 debug 细化,比按业务页面测试更省力精确。
- 开发和设计并行:讲 UI 按组件模块化开发,可以让设计和前端开发更紧密的合作,不同项目共享资源。
劣势:
- 时间周期: 相比针对页面一把梭似的开发,针对组件严谨设计api,编写 story,考虑复用性和边缘场景。项目的开发时间周期必然会提高,这对外包的小伙伴来说,可就不友好了。
- 页面类应用:整个应用在开发和设计角度来看,都是几个完整页面的集合,页面间没有太多可以复用的元素,整夜开发和划分组件差别不大(开发各种可视化大屏可能会有同感)
所以,组件驱动开发,更适合复用场景丰富的系列应用场景,项目生命周期充足,质量要求高,前端和设计合作紧密的团队(或者,咱们自己一个人包圆儿的个人项目😁)
总结
本篇简单介绍了 Storybook 这一组件开发工具,主要包含几点:
- 在已有的 Angular 项目下,通过一行命令初始化了 Storybook 开发环境,它自动安装了依赖,创建了默认配置,并在项目里生成了三个示例。运行它设置好的命令行,即可看到组件测试界面。
介绍了如何编写组件的 story。
- 编写按键组件的三个 story 用例,实现用例的复用。
- 用 parameter 配置背景插件。
- 用 decorator 给组件添加额外的外层 DOM、传递组件的模块依赖。
- 介绍组件驱动开发模式,分析其优劣场景。
纸上得来终觉浅,还是得把它融入咱们实际的开发场景,看看它是如何驱动项目的开发的。所以,一起来体验可视化的测试驱动开发,完成一个完整的业务页面吧,咱们下一篇见!😎
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。