Storybook 元件驅動開發 (CDD) 與文件化
Storybook 是一個開源工具,用於獨立地建構 UI 元件。它提供了一個沙盒環境,讓你可以在隔離的情況下開發和測試元件,而不需要開啟整個應用程式或依賴複雜的後端資料。
這對於大型專案或 Design System 的開發至關重要,我們稱之為 Component-Driven Development (CDD)。
安裝
在現有的 React 專案(如 Vite)中初始化 Storybook 非常簡單:
npx storybook@latest init
這個指令會自動偵測你的專案類型,安裝必要的依賴,並在專案中建立 .storybook 資料夾和一些範例 stories。
啟動 Storybook:
npm run storybook
什麼是 Story? (CSF 格式)
Storybook 使用 Component Story Format (CSF),這是基於 ES6 Modules 的標準格式。一個 Story 檔案通常包含兩部分:Meta (預設匯出) 和 Stories (具名匯出)。
假設有一個按鈕元件 Button.jsx:
// Button.stories.jsx
import { Button } from './Button';
// 1. Meta (預設匯出): 定義這個元件的整體設定
export default {
title: 'Example/Button', // 在側邊欄的路徑 (Category/Component)
component: Button, // 要測試的元件
tags: ['autodocs'], // 自動生成文件頁面
argTypes: {
// 自定義控制項 (可選)
backgroundColor: { control: 'color' },
},
};
// 2. Stories (具名匯出): 每個匯出代表元件的一種「狀態」
// 這裡定義了一個叫 "Primary" 的 Story
export const Primary = {
args: {
primary: true,
label: 'Button',
},
};
// 這是另一個叫 "Secondary" 的 Story
export const Secondary = {
args: {
label: 'Button',
},
};
// 這是 "Large" 尺寸的 Story
export const Large = {
args: {
size: 'large',
label: 'Button',
},
};
關鍵觀念:Args 即 Props
在上面的範例中,觀察 args 物件:
args: {
primary: true,
label: 'Button',
}
這等同於你在 React 程式碼中這樣寫:
<Button primary={true} label="Button" />
Storybook 會自動把 args 傳進去當作元件的 props。這就是為什麼修改 Storybook 面板上的數值,元件會即時更新的原因。
ArgTypes & Controls (控制項)
Args (Arguments) 不僅是傳給元件的 Props,它們還定義了 Storybook UI 面板上的控制項。你可以透過 argTypes 來自訂控制項的類型:
export default {
title: 'Example/Button',
component: Button,
argTypes: {
// 顏色選擇器
backgroundColor: { control: 'color' },
// 下拉選單
variant: {
control: 'select',
options: ['primary', 'secondary', 'outline'],
},
// Radio 按鈕
size: {
control: 'radio',
options: ['small', 'medium', 'large'],
},
// 範圍滑桿
borderRadius: {
control: { type: 'range', min: 0, max: 20, step: 2 },
},
// 日期選擇器
createdAt: { control: 'date' },
},
};
Decorators (裝飾器)
如果你需要在 story 外層包裹額外的元件(例如 ThemeProvider, ReduxProvider 或設定全域樣式),可以使用 Decorators。
常見應用:提供 Context
許多元件依賴 Context (如 Theme, Redux, Router)。我們必須在 Decorator 中提供這些 Provider。
// .storybook/preview.js
import { ThemeProvider } from 'styled-components';
import { BrowserRouter } from 'react-router-dom';
import { theme } from '../src/theme';
export const decorators = [
(Story) => (
<BrowserRouter>
<ThemeProvider theme={theme}>
<div style={{ margin: '3em' }}>
<Story />
</div>
</ThemeProvider>
</BrowserRouter>
),
];
Parameters (參數設定)
parameters 用來設定 Stories 的靜態元數據 (Metadata),通常用於設定 Addon 的行為。
// Button.stories.jsx
export default {
title: 'Button',
component: Button,
parameters: {
// 設定背景色
backgrounds: {
default: 'dark',
values: [
{ name: 'white', value: '#ffffff' },
{ name: 'dark', value: '#333333' },
],
},
// 設定 Viewport (RWD 測試)
viewport: {
defaultViewport: 'iphone6',
},
},
};
Loaders & Mocking (資料模擬)
當元件需要打 API 時,我們不希望它真的送出請求。Storybook 提供了 loaders (在渲染前載入資料) 或搭配 MSW (Mock Service Worker) 來攔截請求。
使用 Loaders (簡單版):
export const UserProfile = {
// loader 會在 story 渲染前執行
loaders: [
async () => ({
user: await (await fetch('https://jsonplaceholder.typicode.com/users/1')).json(),
}),
],
render: (args, { loaded: { user } }) => <UserProfileComponent user={user} {...args} />,
};
Interaction Testing (互動測試)
Storybook 內建了 play 函式,允許你模擬使用者操作並進行斷言 (基於 Testing Library)。這讓你可以在 Storybook 中直接跑整合測試。
import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
export const LoginProcess = {
render: () => <LoginForm />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// 模擬輸入
await userEvent.type(canvas.getByLabelText(/email/i), 'test@example.com');
await userEvent.type(canvas.getByLabelText(/password/i), 'password123');
// 模擬點擊
await userEvent.click(canvas.getByRole('button', { name: /login/i }));
// 斷言驗證
await expect(canvas.getByText('Welcome back!')).toBeInTheDocument();
},
};
Documents (自動生成文件)
Storybook 會自動根據你的 stories 和 PropTypes/TypeScript 介面生成文件。你也可以使用 MDX (Markdown + JS) 來撰寫更豐富的文件。
啟用自動文件生成:
// Button.stories.jsx
export default {
title: 'Example/Button',
component: Button,
tags: ['autodocs'], // 加上這個 tag
};
Addons (插件)
Storybook 擁有豐富的插件生態系:
- Controls: (內建) 動態修改 props。
- Actions: (內建) 記錄事件 callback (如
onClick)。 - Viewport: 測試不同螢幕尺寸的響應式效果。
- Accessibility (a11y): 檢查元件是否符合無障礙標準。
安裝 A11y 插件:
npm install @storybook/addon-a11y --save-dev
部署 (Deployment)
Storybook 建置出來的是靜態檔案,可以部署到任何靜態網站託管服務 (GitHub Pages, Vercel, Netlify)。
# 建置靜態檔 (預設輸出到 storybook-static 資料夾)
npm run build-storybook
Chromatic (推薦)
Chromatic 是 Storybook 官方維護的部署與視覺回歸測試 (Visual Regression Testing) 平台。它可以自動比對每次 commit 的 UI 差異,防止 UI 跑版。
# 安裝
npm install --save-dev chromatic
# 部署
npx chromatic --project-token=<your-project-token>
總結
Storybook 透過將 UI 開發與業務邏輯分離,極大地提升了開發效率。它是建立 Design System 的核心工具,也是團隊協作(設計師、開發者、PM)的重要橋樑。