StoryBook實(shí)戰(zhàn)

原文鏈接
對于一名前端開發(fā)者,必須面對的就是組件化開發(fā)。我做Angular開發(fā)已經(jīng)有些日子了,也曾為自己的項(xiàng)目開發(fā)過通用組件,但僅是在項(xiàng)目內(nèi)部使用,而且是直接用業(yè)務(wù)界面對組件進(jìn)行測試。如果其他項(xiàng)目要使用這些組件,也是使用老土的拷貝方式來進(jìn)行復(fù)用。偶然發(fā)現(xiàn)StoryBook,研究了下,頓生好感,原來組件開發(fā)可以這么簡單的管理和測試,還可以編寫清晰明了的說明文檔,對提升組件開發(fā)的效率那是大大滴提升。

本文將以一個(gè)基于Material的三級(jí)選擇組件為例,進(jìn)行StoryBook實(shí)戰(zhàn),實(shí)實(shí)在在滴體驗(yàn)下StoryBook的強(qiáng)大。

術(shù)語解釋

StoryBook給開發(fā)這提供了一個(gè)強(qiáng)大的組件開發(fā)的生態(tài)環(huán)境,涉及組件的測試、實(shí)景展示、文檔,以及術(shù)語。常見術(shù)語解釋如下:

  1. Addon:類似Plugin,StoryBook中的功能組件以及擴(kuò)展以Addon形式存在,開發(fā)者亦可以自行編寫Addon來擴(kuò)展StoryBook的功能;
  2. Story:類似用例,是組件的各種使用場景;
  3. Decorator:就是給Story做個(gè)包裝,可以是樣式包裝、模塊元數(shù)據(jù)包裝、類型包裝等。
  4. Notes:備注,可以為每個(gè)Story設(shè)置Notes,支持MarkDown語法。

如下是常用的Addon:

  • addon-actions:組件操作事件,如click、change
  • addon-links:鏈接,如某個(gè)Story中單擊按鈕,鏈接到另一個(gè)Story中
  • addon-notes:Story的備注
  • addon-options:調(diào)整StoryBook的外觀
  • addon-knobs:在頁面上改變變量

對于StoryBook入門內(nèi)容,在網(wǎng)上可以找到很多,同時(shí)StoryBook for Angular中也有入門級(jí)的詳細(xì)解釋,這里不贅述。 下面來個(gè)實(shí)戰(zhàn)演練。

創(chuàng)建自己的組件

三級(jí)級(jí)聯(lián)選擇組件,在項(xiàng)目中比較常用,比如省市區(qū)、多級(jí)分類等,通用的UI大多僅提供一級(jí)選擇組件。三級(jí)選擇則需要根據(jù)業(yè)務(wù)需求,開發(fā)者自己編寫。于是我自己寫了一個(gè)省市區(qū)的三級(jí)選擇的組件。
首先為這個(gè)組件創(chuàng)建了獨(dú)立的Angular工程:

ng new cityselect
cd cityselect

組件目錄如下:


目錄結(jié)構(gòu)

組件名稱為MatCascaderComponent,期望的運(yùn)行效果如下:


期望的運(yùn)行效果

StoryBook實(shí)戰(zhàn)

在cityselect中安裝StoryBook,命令如下:

npx -p @storybook/cli sb init --type angular

//同時(shí)安裝如下Addon
npm install --save @storybook/addon-options

執(zhí)行 npm run storybook 成功后,訪問 http://localhost:6006 可以看到StoryBook的界面以及缺省的Story。

StoryBook界面

寫個(gè)簡單的Story

StoryBook提供了兩種Story的寫法,第一種是直接使用組件,第二種是使用Html標(biāo)簽。

直接使用組件

由于組件使用到了Materail的相關(guān)Module,Story中需要先將這些外部Module引入,這里用到了:moduleMetadata。

可以單獨(dú)對每個(gè)Story設(shè)置moduleMetadata:

.add( "直接使用組件", () => ({
    component: MatCascaderComponent,  //直接使用組件
    props: {},
    //僅對當(dāng)前Story生效
    moduleMetadata:{  
      imports: [BrowserAnimationsModule, MatFormFieldModule, MatSelectModule],
      schemas: [],
      declarations: [],
      providers: [CityCascsdeService, CommonService]
    } 
  }),
  { notes: `缺省是三級(jí)地區(qū)選擇` }
)

亦可使用addDecorator對storiesOf下的所有Story設(shè)置moduleMetadata:

.addDecorator(
  //對此storiesOf下的所有Story生效
  moduleMetadata({  
    imports: [BrowserAnimationsModule, MatFormFieldModule, MatSelectModule],
    schemas: [],
    declarations: [],
    providers: [CityCascsdeService, CommonService]
  })
)
.add( "直接使用組件", () => ({
    component: MatCascaderComponent, //直接使用組件
    props: {}
  }),
  { notes: `缺省是三級(jí)地區(qū)選擇` }
);

使用HTML標(biāo)簽

下面我們使用Html標(biāo)簽,這個(gè)我們項(xiàng)目中的用法是一樣的,所以需要對組件MatCascaderComponent進(jìn)行聲明,同樣是在moduleMetadata中:

.addDecorator(
  moduleMetadata({
    imports: [BrowserAnimationsModule, MatFormFieldModule, MatSelectModule],
    schemas: [],
    declarations: [MatCascaderComponent], //這里聲明下
    providers: [CityCascsdeService, CommonService]
  })
)
.add("使用HTML標(biāo)簽",() => ({
    template: `<ngx-mat-cascader ></ngx-mat-cascader>`,
    props: {}
  }),
  { notes: `缺省是三級(jí)地區(qū)選擇` }
);

此時(shí)看到的StoryBook的效果如下:

CSS未引入

引入外部CSS

上面我們看到組件的樣式不對。StoryBook不會(huì)自動(dòng)引入組件需要的CSS文件,需要告訴StoryBook訪問哪個(gè)靜態(tài)文件,詳情可參見參考文檔。

這里給出主要改動(dòng):

  1. .storybook 目錄下創(chuàng)建名為 preview-head.html 文件,該文件是為HTML添加自定義的Head內(nèi)容。內(nèi)容如下:

    <link rel="stylesheet" href="./styles.css" />
    
  2. 在src/styles.css文件中引入Materail的CSS:

    @import "../node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
    @import "../node_modules/bootstrap-material-design/dist/css/bootstrap-material-design.css"
    
  3. package.json文件中修改啟動(dòng)StoryBook的命令,通過 -s 參數(shù)指定靜態(tài)目錄

    //指定./ 和 ./src均為靜態(tài)目錄
    "scripts": {
        "storybook": "start-storybook -p 6006 -s ./,./src",
    }
    

.storybook目下的修改,必須重啟StoryBook。這時(shí)我們看到樣式正確了。


正確CSS

設(shè)置參數(shù)的Story

上面的Story非常簡單,沒有參數(shù)和Action。下面看看如果設(shè)置參數(shù)和Action。MatCascaderComponent缺省是個(gè)省市區(qū)的三級(jí)選擇組件,但是如果提供不同的參數(shù),它就會(huì)華麗的變身了:

.add( "設(shè)置選擇數(shù)據(jù)",() => ({
  component: MatCascaderComponent,
  props: {
    data: [
      {
        code: "11",
        name: "易耗品",
        children: [
          {
            code: "1101",
            name: "打印機(jī)",
            children: [
              { code: "110101", name: "彩色墨盒" },
              { code: "110102", name: "黑色墨盒" }
            ]
          }
        ]
      },
      {
        code: "12",
        name: "食品",
        children: [
          {
            code: "1201",
            name: "快餐",
            children: [
              { code: "120101", name: "薯?xiàng)l" },
              { code: "120102", name: "熱狗" }
            ]
          }
        ]
      }
    ],
    level1placeholder: "選擇分類",
    level2placeholder: "選擇貨區(qū)",
    level3placeholder: "選擇貨架",
    allTitle: "全部",
    showAll: true,
    onZoneChange:action('onZoneChange')
  }
  }),
  { notes: '這是一個(gè)三級(jí)商品選擇組件' }
);

效果如下:


ezgif.com-video-to-gif (1).gif

Html標(biāo)簽測試設(shè)置參數(shù)參見如下示例代碼:

.add( "設(shè)置初始值", () => ({
    template: `<ngx-mat-cascader [data]="basedatas" [separate]="separate" [(value)]="selectvalue" (onZoneChange)="zoneChange()" ></ngx-mat-cascader>`,
    props: {
      basedatas:[...],
      separate:'-',
      selectvalue:'11-1101',
      zoneChange:action('change')
    }
  }),
  { notes: '這是一個(gè)三級(jí)商品選擇組件' }
);

是不是方便的不能再方便?對拷貝、粘貼深惡痛絕的我,看到了組件開發(fā)的春天。

為Story寫備注

在上面的示例中,你會(huì)發(fā)現(xiàn)notes屬性,就是備注的意思。一個(gè)完美的備注即提升了Story的可讀性,也方便后期對組件的維護(hù),更可以通過備注向使用者展示展示自己的組件。
StoryBook提供了兩種途徑,一是在Story的notes屬性中直接使用MarkDown,二是使用一個(gè)MarkDown文件。
如下是在notes屬性中直接使用MarkDown,需要注意的是折行后前面不能有空格:

.add("設(shè)置選擇數(shù)據(jù)", () => ({
    component: MatCascaderComponent,
    props: {...}
  }),
  { notes: ` # 我是一級(jí)標(biāo)題 
## 我是二級(jí)標(biāo)題,行首不能有空格,下同
### 我是三級(jí)標(biāo)題
1. 我是列表1
2. 我是列表2
    ` }
 )

效果如下:


notes中使用MarkDown

如果備注一兩句能說描述清楚Story,上述方法可行。但對于復(fù)雜的Story,還是一個(gè)MarkDown文件更方便。要使用md文件做備注,需要做些改動(dòng),要讓代碼識(shí)別出MarkDown文件:

//引入對md文件的支持,在.storybook目錄下創(chuàng)建typings.d.ts文件,內(nèi)容如下:
declare module "*.md" {
  const content: string;
  export default content;
}

// 在.storybook/tsconfig.json文件添加:

"files": [
    "./typings.d.ts"
  ]

// story中引入文件:
  
import * as readme from '../app/components/select/README.md';

.add("選擇測試",() => ({
    component: MatCascaderComponent,
    props: {...}
  }),
  { notes: readme }
);

效果如下:


使用MarkDown文件

是不是有點(diǎn)喜歡上StoryBook了?至少我是這樣,甚至憧憬著自己開發(fā)的組件減輕了更多同行的工作量。

更換主題

StoryBook還可以打包成靜態(tài)頁面放在公網(wǎng)上,供大家品評。但是界面左上角顯示的還是StoryBook,需要換下,這就涉及的修改StoryBook的主題了,這里僅提供名字和鏈接更改的方法:

import { addParameters } from '@storybook/angular';
import logo from '../src/assets/img/dteam.svg';

addParameters({
  options: {
    theme:{
      brandTitle:'DTeam組件庫',
      brandUrl: 'https://github.com/dteam-top'
    },
  }
});

效果如下:


更換主題

關(guān)于主題的更詳細(xì)的說明,請參見文檔2。

總結(jié)

通過對StoryBook的學(xué)習(xí)和實(shí)踐,我覺得它的確不錯(cuò),對于組件的開發(fā)、測試、文檔化非常方便:

  1. 支持的主流前端框架
  2. 為組件提供獨(dú)立的開發(fā)環(huán)境
  3. 多種測試場景,全面測試組件
  4. 使用MarkDown編寫備注
  5. 眾多的Addon,方便擴(kuò)展

這些對于前端開發(fā)的工作,提供了不少改進(jìn):

  • 避免混亂的重復(fù)代碼,提高代碼復(fù)用性
  • 測試人員可以直接測試組件
  • 豐富的Story,所見即所得
  • 組件開發(fā),督促開發(fā)者提高自身技能
  • Markdown的文檔,方便展示組件

但是“人無完人”,我在使用StoryBook過程中,也發(fā)現(xiàn)了一些問題:

  1. Addon太多,管理有些混亂
  2. 支持UI框架多,但是有的Andon卻不是所有UI框架都支持,比如info,具體可參見文檔3
  3. 大版本之間變化大,從網(wǎng)上找到的示例代碼比較老,不能用
  4. 不知道是否支持國際化

任何工具都是入門易、深耕難,StoryBook亦是如此,這需要開發(fā)人員提升組件化思維,并結(jié)合更多的實(shí)踐,才能讓它更好的助力前端開發(fā)。

參考文檔

  1. StoryBook for Angular
  2. StoryBook Theme
  3. StoryBook addon對各前端平臺(tái)的支持
  4. StoryBook for Angular在線示例
  5. StoryBook引用靜態(tài)資源
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容