方格精選

什麼是 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 - 跨領域轉職的軟體工程師
449會員
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
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
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 進階應用 ,其中包含單筆資料、多筆資料。
Thumbnail
1.設計系統不用從頭開始 在設計產品時,有一個觀念可能會顛覆我們對於產品設計的傳統想法。這是初期在 AlleyPin 擔任一人設計師,負責各種產品或視覺設計工作時才逐漸領悟到的一點。 當時,我在購買UI Kits這件事情上猶豫不決,擔心使用現成的設計資源會使我的設計變得無聊或是缺乏創造。後來面臨
Thumbnail
1.設計系統不用從頭開始 在設計產品時,有一個觀念可能會顛覆我們對於產品設計的傳統想法。這是初期在 AlleyPin 擔任一人設計師,負責各種產品或視覺設計工作時才逐漸領悟到的一點。 當時,我在購買UI Kits這件事情上猶豫不決,擔心使用現成的設計資源會使我的設計變得無聊或是缺乏創造。後來面臨
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News