专栏首页全栈修仙之路Angular 工具篇之Storybook

Angular 工具篇之Storybook

Storybook 是一个 UI 组件的开发环境。它允许你能够浏览一个组件库,查看每个组件的不同状态,以及支持交互式的方式开发和测试组件。

Storybook 在你的应用程序之外运行。这允许你能够独立的开发 UI 组件,你可以提高组件的可重用性、可测试性和开发速度。你可以快速构建,而无需担心应用程序特定的依赖项。

这里有一些可以参考的特色示例,可以了解 Storybook 的工作原理。Storybook 这款工具很强大,它支持很多流行的框架,比如:

  • React
  • React Native
  • Vue
  • Angular
  • Polymer
  • Riot

接下来我们来介绍一下在 Angular 项目中如何使用 storybook。现在我们使用 Angular CLI 来创建一个新的演示项目:

$ ng new angular-storybook-demo
$ cd angular-storybook-demo

这里需要注意的是,本文使用的 CLI 版本为:

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/

Angular CLI: 6.1.5
Node: 9.11.0
OS: darwin x64
Angular: 6.1.6

接下来安装 @storybook/cli

$ npm i -g @storybook/cli

成功安装以上依赖后,在命令行运行 getstorybook 命令初始化 storybook,该命令会为我们自动生成以下两个 npm script 命令:

"scripts": {
   "storybook": "start-storybook -p 6006",
   "build-storybook": "build-storybook"
}

上面的 storybook 命令,通过 -p 参数用于指定 storybook 的端口。对于基础的 Storybook 配置文件,我们只需简单地告诉 Storybook 从哪里获取 stories。

getstorybook 命令运行后,会自动为我们创建一个 .storybook 目录。然后在该目录下分别创建两个文件:config.js 和 addons.js 文件。顾名思义 config.js 文件就是配置文件,该文件包含以下内容:

import { configure } from '@storybook/angular';

// automatically import all files ending in *.stories.ts
const req = require.context('../src/stories', true, /.stories.ts$/);
function loadStories() {
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);

上面的代码支持从 ../src/stories 目录下自动导入以 *.stories.ts 结尾的文件。当然你也可以指定从其它目录加载。通过上面的两个步骤,我们已经完成 Storybook 的初始化工作。此外 getstorybook 命令还会在 src/stories 目录下创建一个 index.stories.ts 文件:

import { storiesOf } from '@storybook/angular';
import { withNotes } from '@storybook/addon-notes';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';

import { Welcome, Button } from '@storybook/angular/demo';

storiesOf('Welcome', module).add('to Storybook', () => ({
  component: Welcome,
  props: {},
}));

storiesOf('Button', module)
  .add('with text', () => ({
    component: Button,
    props: {
      text: 'Hello Button',
    },
  }))
  .add(
    'with some emoji',
    withNotes({ text: 'My notes on a button with emojis' })(() => ({
      component: Button,
      props: {
        text: '? ? ? ?',
      },
    }))
  )
  .add(
    'with some emoji and action',
    withNotes({ text: 'My notes on a button with emojis' })(() => ({
      component: Button,
      props: {
        text: '? ? ? ?',
        onClick: action('This was clicked OMG'),
      },
    }))
  );

storiesOf('Another Button', module).add('button with link to another story', () => ({
  component: Button,
  props: {
    text: 'Go to Welcome Story',
    onClick: linkTo('Welcome'),
  },
}));

在上面的示例中,我们通过调用 storiesOf() 方法后返回的对象的 add() 方法来创建故事。其中 add() 方法支持以下参数:

  • storyName: string —— 故事的名称;
  • getStory: IGetStory —— 一个函数对象,调用后返回一个配置对象,包含 component、props 等属性。这里 IGetStory 类型的定义如下:
export type IGetStory = () => {
  props?: ICollection;
  moduleMetadata?: Partial<NgModuleMetadata>;
  component?: any;
  template?: string;
};

通过 @storybook/addon-actions 库中导入的 action 方法,我们能够方便地记录用户触发的自定义事件。此外利用 @storybook/addon-notes 这个库导入的 withNotes() 方法,我们还可以为每个故事添加一个备注信息。

好的,这时一切看起来很顺利,但当我们运行 npm run storybook 命令时,控制台会抛出异常信息。

通过查看 Github 上 Storybook 项目中的 issue,我们发现了异常的原因。即对于 Angular CLI 6 创建的项目需要安装 @storybook/angular@storybook/addons 这两个库 4.0 以上的版本,实际测试发现还得手动安装 @babel/core 这个依赖库。

$ npm i @storybook/angular@4.0.0-alpha.20 @storybook/addons@4.0.0-alpha.20 --save-dev
$ npm i @babel/core@7.0.0 --save-dev

在成功安装完以上依赖后,我们再次运行 npm run storybook 命令,这时打开 http://localhost:6006/ 地址,你将会看到以下内容:

以上截图中所演示的 Button 组件的定义如下:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'storybook-button-component',
  template: `
      <button (click)="onClick.emit($event);">{{ text }}</button>
  `,
  styles: [
    `
      button {
        border: 1px solid #eee;
        border-radius: 3px;
        background-color: #ffffff;
        cursor: pointer;
        font-size: 15px;
        padding: 3px 10px;
        margin: 10px;
      }
    `,
  ],
})
export default class ButtonComponent {
  @Input() text = '';
  @Output() onClick = new EventEmitter<any>();
}

上面的 ButtonComponent 组件很简单,而在实际的项目中我们的组件可能需要使用 Angular 内置的指令(如 ngIf 或 ngFor)或第三方库的组件。针对这种情况,我们就可以利用配置对象的 moduleMetadata 属性:

import { CommonModule } from '@angular/common';
import { storiesOf } from '@storybook/angular';
import { MyButtonComponent } from '../app/my-button/my-button.component';
import { MyPanelComponent } from '../app/my-panel/my-panel.component';
import { MyDataService } from '../app/my-data/my-data.service';

storiesOf('My Panel', module)
  .add('Default', () => ({
    component: MyPanelComponent,
    moduleMetadata: {
      imports: [CommonModule],
      schemas: [],
      declarations: [MyButtonComponent],
      providers: [MyDataService],
    }
  }));

上面示例中,我们为每个 story 单独设置 moduleMetadata 属性。若每个 story 都使用同样的 Metadata 信息,我们就可以通过 addDecorator() 方法,统一设置 moduleMetadata 属性:

import { CommonModule } from '@angular/common';
import { storiesOf, moduleMetadata } from '@storybook/angular';
import { MyButtonComponent } from '../app/my-button/my-button.component';
import { MyPanelComponent } from '../app/my-panel/my-panel.component';
import { MyDataService } from '../app/my-data/my-data.service';

storiesOf('My Panel', module)
  .addDecorator(
    moduleMetadata({
      imports: [CommonModule],
      schemas: [],
      declarations: [MyButtonComponent],
      providers: [MyDataService],
    })
  )
  .add('Default', () => ({
    component: MyPanelComponent
  }))
  .add('with a title', () => ({
    component: MyPanelComponent,
    props: {
      title: 'Foo',
    }
  }));

以上关于 moduleMetadata 的使用示例来源于 Storybook 官方的 guide-angular 文档,感兴趣的同学可以阅读一下该文档。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • RxJS 处理多个Http请求

    有时候进入某个页面时,我们需要从多个 API 获取数据然后进行页面显示。管理多个异步数据请求会比较困难,但我们可以借助 Angular Http 服务和 RxJ...

    阿宝哥
  • Angular 6.x 快速入门

    在 Angular 中,我们可以通过 Component 装饰器和组件类来创建自定义组件。

    阿宝哥
  • Angular HttpClient 拦截器

    在之前的 Angular 6 HttpClient 快速入门 文章中,我们已经简单介绍了 Http 拦截器。本文将会进一步分析一下 Http 拦截器。拦截器提供...

    阿宝哥
  • 使用storybook管理React组件

    2018年10月storybook发布了4.0版本,在UI层支持、构建、移动端、stroy参数等多个方面进行了升级优化。本文已React的UI组件为例,演示如何...

    IMWeb前端团队
  • 短文本分析----基于python的TF-IDF特征词标签自动化提取

    最近做课题,需要分析短文本的标签,在短时间内学习了自然语言处理,社会标签推荐等非常时髦的技术。我们的需求非常类似于从大量短文本中获取关键词(融合社会标签和时间属...

    用户1539362
  • 广告小程序后端开发(5.安装配置django-rest-framework,编写后台登录逻辑)

    玩蛇的胖纸
  • 分布式系统中的定时任务全解(一)

    在网站系统里面定时任务是一个重要和不可缺的角色,很多地方需要使用定时执行一项任务。比如,订单系统的接单超时、支付超时,结算系统的定时结算、奖励计算,第三方的认证...

    九州暮云
  • redis命令之操作有序集合

    Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为...

    无邪Z
  • 经典面试题-FileSystemResource和ClassPathResource有何区别?

    在FileSystemResource 中需要给出spring-config.xml文件在你项目中的相对路径或者绝对路径。在ClassPathResource中...

    cwl_java
  • Springboot 系列(二)Spring Boot 配置文件

    不管是通过官方提供的方式获取 Spring Boot 项目,还是通过 IDEA 快速的创建 Spring Boot 项目,我们都会发现在 resource 有一...

    未读代码

扫码关注云+社区

领取腾讯云代金券