方格精選

什麼是 Storybook?獨立開發前端元件,打造自己的 UI Library(React)

更新 發佈閱讀 16 分鐘

Storybook 是一個用來透過獨立元件快速開發 UI 介面的工具,以往要開發元件時,我們可能需要建立一個全新的頁面才能進行開發,但這樣的開發方式可能會有一個狀況:沒有辦法事先開發或是預覽流程中不存在的元件。

透過 Storybook 我們在開發元件時,不需要重新建立複雜的頁面結構,而是可以擁有獨立的工作區進行元件的獨立開發,同時能在元件塞入靜態、動態資料,建立屬於自己的元件庫,或是達到元件文件化的目的:

raw-image

這樣開發出來的文件複用性更高,除了上方提到的特色外, Sotrybook 自動綁定 UI 測試,還可以透過擴充功能(addons)讓 storybook 可以動態切換狀態,解決開發者找不到共用元件而重複開發元件、狀態等痛點。

安裝 Storybook

首先我們可以透過以下指令安裝 Storybook CLI,要注意不要使用空的專案,建議要使用既有開發環境來初始化 Storybook,我這邊是使用 Vite React TypeScript 框架來輔助:

$ npx storybook@latest init

成功初始化 Storybook 後,會發現在 src 資料夾下自動生成了 stories 資料夾,這個資料夾是 storybook 初始化所提供的範例,啟動後會自動獲得一份 Storybook 指引手冊:

vite-project
├──src/
│ ├──stories/
│ │ ├──assets/
│ │ │ ├─... -> 指引手冊中會用到的一些 icon
│ │ │ ├─Button/
│ │ ├──button.css
│ │ ├──Button.stories.ts
│ │ ├──Button.tsx
│ │ ├──header.css
│ │ ├──Header.stories.ts
│ │ ├──Header.tsx
│ │ ├──page.css
│ │ ├──Page.stories.ts
│ │ ├──Page.tsx
│ │ ├──introduction.mdx -> 指引手冊頁面
├──...

接著我們就能執行以下指令啟動 storybook:

#npm
$ npm run storybook

# yarn
$ yarn storybook

就可以看到 storybook 依照 src/stories 資料夾底下的檔案自動生成了元件庫,也可以看到官方所提供的指引手冊:

raw-image

什麼是 Story ?

在 storybook 中,我們會以 story 來稱呼某元件在特定狀態底下所呈現出來的示例。

我們會使用 <元件>.stories.ts 的檔案來捕捉元件被渲染的狀態,這個 story 可以有不同的屬性、狀態,我們透過 Stroybook 內建、擴充工具(addons)來動態切換這些元件的狀態、尺寸、呈現方式,藉此 story 了解元件使用的方式與情境。

舉例來說,初始化範本中的按鈕元件可以動態切換樣式、文字內容、背景顏色、尺寸,下方為初始狀態:

raw-image

我們來試著透過 storybook 提供的介面改變按鈕的樣式:

raw-image

如果這樣還沒辦法充分展現 Storybook 的厲害之處,我補充一些它能派上用場的時機點:

  • 撰寫專案的 Design System
  • 開發專案共用元件
  • 與 PM、Designer 協作

在以上情境使用 Storybook 作為開發工具,能有效避免專案進行重工,接著就讓我們使用 Storybook 所提供的範例來了解怎麼撰寫自己的 UI 元件庫吧!

Story 寫在哪比較好?

在撰寫 Story 前我們先來聊聊關於元件路徑問題,官方文件建議在撰寫 Story 時,指定的元件 Story 建議放在元件檔案的「旁邊」,也就是說在初始化時的 src/stories 資料夾目錄中的配置單純可以參考用(目錄層級可以參考上方安裝的章節)。

我們完全可以依照自己的開發習慣來決定 story 檔案放置的路徑,不會因為不是寫在 src/stories 目錄底下而無法啟動 storybook ,根據安裝工具的不同可能會在不同的資料層層級產生 .storybook 檔案,以及對應的 tsconfig 設置,從這些檔案中可以去調整 storybook 執行時,哪些資料夾層級下的 .stories.ts(tsx)會生效,跟據環境不同,配置檔案的方式與目錄階層也會不同。

我們可以把 Button 元件的資料層級整理成:

vite-project
├──src/
│ ├──components/
│ │ ├──atoms/
│ │ │ ├─Button/
│ │ │ │ ├─index.ts
│ │ │ │ ├─index.css
│ │ │ │ ├─Button.stories.ts

要注意的是經過整理的資料夾層級因為目錄層級不同,引入的方式也會不同,如果好奇我的元件拆分層級是參考什麼模式,可以參考這篇文章

定義 Story 的基本資料

接著我們就由 Storybook 官方所提供的 Button 元件檔案看看要怎麼定義 story 的基礎資料:

// Button.stories.ts|tsx

import type { Meta } from '@storybook/react';

import { Button } from './Button';

const meta: Meta<typeof Button> = {
title: 'Example/Button',
component: Button,
};

export const Interactive = {}

export default meta;

如果我們要在 React Typescript 中撰寫 story,首先我們要引入一個 Meta 型別,我們可以在這個型別中帶入一些屬性建立 story 的基本資料,例如:title 屬性可以用來建立該 story 在 storybook 中的層級,上方範例中所看到的層級會是:

raw-image


我們可以透過 title 屬性,以路徑的方式來整理我們想要的元件分類。

其次,除了 title 屬性外,component 屬性用來讓 Storybook 知道要渲染哪一個元件,設定好這這兩個屬性後我們再透過不具名的匯出,這非常重要,一定要匯出這一層 storybook 才可以偵測到 storybook 的 Meta 資訊。

接著透過具名匯出的方式,匯出至少一個 story 範本,這個 story 可以是空物件,某一個版本之後 storybook 必須得匯出至少一個 story,才能正確顯示,如果只有匯出單獨 Meta 資訊,storybook 就會報錯,跟你說它找不到這個 story,接著就可以在 Storybook 中看到 Button 元件了。

使用 args 物件載入靜態元件資訊

在 stroybook 中使用具名匯出,可以直接建立一個獨立的 stroy,假設有個想要進行 UI 測試的元件 Button :

// ./atoms/Button/index.ts

import React from 'react';
import classnames from 'classnames';

import styles from './index.module.css';

const Button = ({ className, type, content }) => (
<button className={classnames(styles.button, styles[type], className)}>{content}</button>
);

export default Button;

Storybook 的 args 物件可以協助我們進行靜態 Props、文字的帶入:

// ./atoms/Button/Button.stories.ts
import Button from 'components/atoms/Button';

export default {
title: 'atoms/Button',
component: Button,
};

// 展開元件的屬性
const Template = args => <Button {...args} />;

// 將 story 綁定空物件
export const Interactive = Template.bind({});

// 新增屬性靜態值
Interactive.args = {
type: 'basic',
content: 'Button',
};

即可在 storybook 的 controls 頁籤中,找到我們預設的靜態屬性,並透過面板進行屬性動態調整:

raw-image


使用 argTypes 優化 controls 介面

透過 args 所建立的 storybook controls 介面,輸入的內容會是自定義的字串,這會有個問題:協作者會不曉得元件還有哪些既有的屬性可以使用。

因此,我們可以透過使用 argTypes 來優化 controls 介面, argTypes 物件讓開發者可以用更直覺且互動性的方式動態調整元件狀態:

// ./atoms/Button/__test__/Button.stories.ts
import Button from 'components/atoms/Button';

export default {
title: 'atoms/Button',
component: Button,
};

const Template = args => <Button {...args} />;

export const Interactive = Template.bind({});
Interactive.argTypes = {
type: { // Button 元件的 type 屬性
options: ['primary', 'secondary'],
control: { type: 'select' },
},
content: { control: 'text' }, // Button 元件的 content 屬性
};

argTypes 物件第一層級的屬性為「元件屬性」,在這些元件屬性中,我們可以用其他的控制項來優化 storybook controls 介面,例如: control 屬性可以指定這些控制項要以哪種 UI 型態呈現,以及要用什麼樣的內容給定預設值,以上方的 Button.stories.ts 來說,會這樣呈現:

raw-image

argTypes 語法 & 使用情境

raw-image

(希望未來方格子的 Markdown 可以支援表格,我這邊暫時先用截圖代替)

storybook 提供的 argTypes interface:

{
[key: string]: {
control?: ControlType | { type: ControlType; /* See below for more */ } | false;
description?: string;
if?: Conditional;
mapping?: { [key: string]: { [option: string]: any } };
name?: string;
options?: string[];
table?: {
category?: string;
defaultValue?: { summary: string; detail?: string };
disable?: boolean;
subcategory?: string;
type?: { summary?: string; detail?: string };
},
type?: SBType | SBScalarType['name'];
}
}

使用 Actions 模擬事件觸發

除了透過 control addon 協助切換元件狀態,storybook 提供 action addon,檢驗 UI 是否有正確觸發事件,我們可以透過 argTypes 屬性來告訴 storybook 哪個元件屬性需要被觸發事件:

// Button.stories.js|jsx|ts|tsx

import { Button } from './Button';

export default {
title: 'Button',
component: Button,
argTypes: { onClick: { action: 'clicked' } }, // 事件名稱,可以自定義
};

點擊按鈕時即可在 Actions 頁籤中看到事件物件:

raw-image

或是可以使用 @storybook/addon-actions 所提供的 action 方法,也能模擬點擊事件:

import { action } from '@storybook/addon-actions';
import Button from 'components/atoms/Button';

export default {
title: 'atoms/Button',
component: Button,
};

// action 方法的第一個參數為自定義 onClick 事件名稱
const Template = args => <Button {...args} onClick={action('on click')} />;

如果想要模擬更多事件,則可以使用 @storybook/addon-actions 所提供的 actions 方法,展開後帶入事件名稱,來檢核各種事件是否有被正確觸發:

import { actions } from '@storybook/addon-actions';
import Button from 'components/atoms/Button';

export default {
title: 'atoms/Button',
component: Button,
};

const Template = args => <Button {...actions('onClick', 'onMouseOver')} {...args} />;

Storybook 的實作模式

針對 storybook 的開發模式,以及要怎麼樣寫才能寫出自己順手,其他人也能無痛使用的功用元件,是一門滿深的功夫,也會跟每個團隊的開發模式有所不同,有時候我們規劃好了一套模式,但有可能因為各式各樣的因素而打亂了。

目前自己遇到最好也最有效率的方式,是在專案中建立元件的模板自動化工具(例如:搭配 node fs 模組之類的),讓開發者建立元件時更加快速、便利,把 storybook 的實作模式強制綁定到這個模板中,就不怕有人在開發的過程中把 storybook 忘記了。

希望這篇文章可以讓大家更了解 storybook 的方便之處,我是 Vivian,我們下次見!


關於我:

一名從英文系畢業的前端工程師,喜歡閱讀、寫東西及自我成長。

|Instagram: Vivian Yeh|vivian_enlife

|聯絡我:vivian.enlife@gmail.com

留言
avatar-img
留言分享你的想法!
avatar-img
Vivian Yeh - 跨領域轉職的軟體工程師
457會員
103內容數
為了追求可以窩在座位上、可以心無旁騖思考問題、座位可以亂七八糟沒關係、不需要到處哈腰點頭跑客戶,不用腳踩十公分、連妝都可以不用化的職場人生,文組少女毅然決然踏上RD的養成日常。
2024/05/23
與 cookie 相比,localStorage 與 sessionStorage 的機制相對單純,兩者皆是瀏覽器中的儲存空間,與 cookie 最大的不同在於:localStorage 與 ⋯⋯
Thumbnail
2024/05/23
與 cookie 相比,localStorage 與 sessionStorage 的機制相對單純,兩者皆是瀏覽器中的儲存空間,與 cookie 最大的不同在於:localStorage 與 ⋯⋯
Thumbnail
2024/05/22
在瀏覽器環境中有許多的儲存空間,想要查看這些空間的話,可以透過「chrome > Dev Tools > Application > Storage」即能進行查看。 瀏覽器內存空間的差異不僅常常被拿來被當作面試考題,在實務開發中更扮演舉足輕重的角色,今天就想透過這系列的文章深度了解這些瀏覽器內存⋯
Thumbnail
2024/05/22
在瀏覽器環境中有許多的儲存空間,想要查看這些空間的話,可以透過「chrome > Dev Tools > Application > Storage」即能進行查看。 瀏覽器內存空間的差異不僅常常被拿來被當作面試考題,在實務開發中更扮演舉足輕重的角色,今天就想透過這系列的文章深度了解這些瀏覽器內存⋯
Thumbnail
2024/03/27
Vite 是由 Vue 開發者 Evan You 所開發出來,用來加快、優化程式碼打包的工具,在這裡我們不免會需要提到大部分前端開發者可能都會聽過、使用過的前端工具:Webpack。 在那之前,我們先來聊聊為什麼前端會需要所謂的打包工具呢?
Thumbnail
2024/03/27
Vite 是由 Vue 開發者 Evan You 所開發出來,用來加快、優化程式碼打包的工具,在這裡我們不免會需要提到大部分前端開發者可能都會聽過、使用過的前端工具:Webpack。 在那之前,我們先來聊聊為什麼前端會需要所謂的打包工具呢?
Thumbnail
看更多
你可能也想看
Thumbnail
臺灣獨立調香師品牌 Sunkronizo,Friday : Sexy Vibe 淡香精,揉合威士忌、菸草、皮革、蜂蜜與花香的多層次魅力,讓知性自信與內斂的從容態度,從視覺、嗅覺都充分表現。
Thumbnail
臺灣獨立調香師品牌 Sunkronizo,Friday : Sexy Vibe 淡香精,揉合威士忌、菸草、皮革、蜂蜜與花香的多層次魅力,讓知性自信與內斂的從容態度,從視覺、嗅覺都充分表現。
Thumbnail
使台劇得以突破過往印象中偶像劇、鄉土劇等範疇,產製更多類型,甚至紅到國外、帶動台灣觀光的最重要原因,便是「隨選串流平台」服務在近十年的蓬勃發展,台灣人愛看串流的程度或許比你我想像中都高,高到連美國電影協會(MPA),都委託Frontier Economics進行研究
Thumbnail
使台劇得以突破過往印象中偶像劇、鄉土劇等範疇,產製更多類型,甚至紅到國外、帶動台灣觀光的最重要原因,便是「隨選串流平台」服務在近十年的蓬勃發展,台灣人愛看串流的程度或許比你我想像中都高,高到連美國電影協會(MPA),都委託Frontier Economics進行研究
Thumbnail
本文探討串流平臺(VOD)如何徹底改變好萊塢和臺灣影視產業的生態。從美國電影協會(MPA)的數據報告,揭示串流服務在臺灣的驚人普及率與在地內容的消費趨勢。文章分析國際作品如何透過在地化元素開拓新市場。同時,作者也擔憂政府過度監管可能扼殺臺灣影視創新自由,以越南為鑑,呼籲以開放態度擁抱串流時代的新機遇
Thumbnail
本文探討串流平臺(VOD)如何徹底改變好萊塢和臺灣影視產業的生態。從美國電影協會(MPA)的數據報告,揭示串流服務在臺灣的驚人普及率與在地內容的消費趨勢。文章分析國際作品如何透過在地化元素開拓新市場。同時,作者也擔憂政府過度監管可能扼殺臺灣影視創新自由,以越南為鑑,呼籲以開放態度擁抱串流時代的新機遇
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
自訂元件生成位置顧名思義就是可以指定部分HTML區塊渲染在特定的畫面上,即使在不同組件也能把A組件內的部分畫面,展現在B組件上,以下方程式舉例。
Thumbnail
自訂元件生成位置顧名思義就是可以指定部分HTML區塊渲染在特定的畫面上,即使在不同組件也能把A組件內的部分畫面,展現在B組件上,以下方程式舉例。
Thumbnail
在程式任何地方都能修改各種react組件狀態的做法分享
Thumbnail
在程式任何地方都能修改各種react組件狀態的做法分享
Thumbnail
樣板模式的定義極為簡單,卻是大型系統程式、WEB/APP應用框架的設計核心,完美展現設計模式的價值: 簡單、高效、強大。
Thumbnail
樣板模式的定義極為簡單,卻是大型系統程式、WEB/APP應用框架的設計核心,完美展現設計模式的價值: 簡單、高效、強大。
Thumbnail
這系列是我在 2023 六角學院 Vue作品實戰班的筆記,筆記以本人理解的方式記錄。此篇主題為 Slot Props 進階應用 ,其中包含單筆資料、多筆資料。
Thumbnail
這系列是我在 2023 六角學院 Vue作品實戰班的筆記,筆記以本人理解的方式記錄。此篇主題為 Slot Props 進階應用 ,其中包含單筆資料、多筆資料。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News