Angular 工具篇之Storybook

2019-11-05 15:50:59 浏览数 (1)

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

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

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

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

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

代码语言:javascript复制
$ ng new angular-storybook-demo
$ cd angular-storybook-demo

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

代码语言:javascript复制
     _                      _                 ____ _     ___
    /    _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △  | '_  / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ | | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   __| |_|__, |__,_|_|__,_|_|       ____|_____|___|
                |___/

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

接下来安装 @storybook/cli

代码语言:javascript复制
$ npm i -g @storybook/cli

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

代码语言:javascript复制
"scripts": {
   "storybook": "start-storybook -p 6006",
   "build-storybook": "build-storybook"
}

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

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

代码语言:javascript复制
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 文件:

代码语言:javascript复制
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 类型的定义如下:
代码语言:javascript复制
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 这个依赖库。

代码语言:javascript复制
$ 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 组件的定义如下:

代码语言:javascript复制
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 属性:

代码语言:javascript复制
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 属性:

代码语言:javascript复制
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 人点赞