方格精選

什麼是 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

為了追求可以窩在座位上、可以心無旁騖思考問題、座位可以亂七八糟沒關係、不需要到處哈腰點頭跑客戶,不用腳踩十公分、連妝都可以不用化的職場人生,文組少女毅然決然踏上RD的養成日常。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
前陣子在跟讀者聊天的時候,發現在 Junior 階段很容易遇到一些工作上挫折,但又不太知道怎麼解決或是優化。 也有可能在開發過程中碰到了些大地雷,但身邊的前輩、同事不一定能用比較軟性的方式好好的傳達,這是非常常出現的,尤其是在跨部門協作經驗較少的工程師,會不曉得怎麼用淺顯易懂的語言告訴⋯⋯
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
前陣子在跟讀者聊天的時候,發現在 Junior 階段很容易遇到一些工作上挫折,但又不太知道怎麼解決或是優化。 也有可能在開發過程中碰到了些大地雷,但身邊的前輩、同事不一定能用比較軟性的方式好好的傳達,這是非常常出現的,尤其是在跨部門協作經驗較少的工程師,會不曉得怎麼用淺顯易懂的語言告訴⋯⋯
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
你可能也想看
Google News 追蹤
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
別急著下筆!寫劇本前,必須花時間找到自己的故事核心,並僅用一句話表述清楚。 本文提供「發展故事核心的 5 個步驟」以及諸多電影的故事核心作為範例
Thumbnail
這是我第一次針對非特定作者,以編輯角度出發而寫的文章,所以每篇文章都會針對最普遍與最重要的概念提出說明與練習。我相信,這些文章一定無法照顧到所有創作者的需求。但如果只有某篇文章某句話能帶給你啟發或安慰,對我來說,那就值得了。
Thumbnail
在程式任何地方都能修改各種react組件狀態的做法分享
這邊是不可能SOP的開坑SOP! 靈感→開坑→設定→填坑→撰稿→校稿→完稿   上一篇是在設計劇情,那麼按照上一篇提到的,我來完整介紹一下示範用劇情!(這裡我用簡單的大綱,方便呈現,完全是劇情設定,不含任何教學成分,可自行跳過。)   需要的元素為:愛情、友誼、冒險、非人類、多種族、異世界、穿
  上一篇廢話一堆(好吧我一直如此)。   靈感→開坑→設定→填坑→撰稿→校稿→完稿   這是我們要細說的步驟,上一篇已經寫了「靈感」。這次進入開坑! ❈   試問,「你想寫什麼類型的故事?」,只要類型就好,這就是第一步。   下面的假設示範,我將使用「最困難最複雜但其實最常見也最容易有漏
  因為我過去完全沒有任何的大綱知識(2020年寫這篇主題前),是看很多人提了之後我才去科普過,這邊僅稍加整理網路資料,並給予一些個人建議:D(現在2024會額外補充一些)   我要先給大家一個觀念:大綱不是必須的,是作者需求而定。   這邊也只說「故事」的大綱,作文論文等其餘不在討論中。
故事的構成,可以簡單分為劇情與角色,基於這兩件,會衍生出更多項目,形成一個密不可分的龐大架構。
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
前言 現在的前端需求已經越來越高,要考慮HTML及CSS的切版美觀程度,以及React以及Flutter所提出的元件(Componet、widget)觀念,也就是將元件模組化,使元件可以更動態的被程式運行,而不用靜態的客製化每一個介面。開發一個好的元件可以提升整體的開發速度,讓任何使用元件的開發者
Thumbnail
前端開發者常會遇到需要網頁素材的情況,雖然在公司中都可能有可以配合的平面設計師或是UIUX設計師,但在這個多工高效的時代不免也需要前端開發者也可以處理簡單的設計,也可提升設計審美或與設計師溝通的能力。 然而前端開發者也算是擁有設計師的天賦,透過程式碼來完成平面設計,將網頁的每個介面都視為平面設計,
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
別急著下筆!寫劇本前,必須花時間找到自己的故事核心,並僅用一句話表述清楚。 本文提供「發展故事核心的 5 個步驟」以及諸多電影的故事核心作為範例
Thumbnail
這是我第一次針對非特定作者,以編輯角度出發而寫的文章,所以每篇文章都會針對最普遍與最重要的概念提出說明與練習。我相信,這些文章一定無法照顧到所有創作者的需求。但如果只有某篇文章某句話能帶給你啟發或安慰,對我來說,那就值得了。
Thumbnail
在程式任何地方都能修改各種react組件狀態的做法分享
這邊是不可能SOP的開坑SOP! 靈感→開坑→設定→填坑→撰稿→校稿→完稿   上一篇是在設計劇情,那麼按照上一篇提到的,我來完整介紹一下示範用劇情!(這裡我用簡單的大綱,方便呈現,完全是劇情設定,不含任何教學成分,可自行跳過。)   需要的元素為:愛情、友誼、冒險、非人類、多種族、異世界、穿
  上一篇廢話一堆(好吧我一直如此)。   靈感→開坑→設定→填坑→撰稿→校稿→完稿   這是我們要細說的步驟,上一篇已經寫了「靈感」。這次進入開坑! ❈   試問,「你想寫什麼類型的故事?」,只要類型就好,這就是第一步。   下面的假設示範,我將使用「最困難最複雜但其實最常見也最容易有漏
  因為我過去完全沒有任何的大綱知識(2020年寫這篇主題前),是看很多人提了之後我才去科普過,這邊僅稍加整理網路資料,並給予一些個人建議:D(現在2024會額外補充一些)   我要先給大家一個觀念:大綱不是必須的,是作者需求而定。   這邊也只說「故事」的大綱,作文論文等其餘不在討論中。
故事的構成,可以簡單分為劇情與角色,基於這兩件,會衍生出更多項目,形成一個密不可分的龐大架構。
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
前言 現在的前端需求已經越來越高,要考慮HTML及CSS的切版美觀程度,以及React以及Flutter所提出的元件(Componet、widget)觀念,也就是將元件模組化,使元件可以更動態的被程式運行,而不用靜態的客製化每一個介面。開發一個好的元件可以提升整體的開發速度,讓任何使用元件的開發者
Thumbnail
前端開發者常會遇到需要網頁素材的情況,雖然在公司中都可能有可以配合的平面設計師或是UIUX設計師,但在這個多工高效的時代不免也需要前端開發者也可以處理簡單的設計,也可提升設計審美或與設計師溝通的能力。 然而前端開發者也算是擁有設計師的天賦,透過程式碼來完成平面設計,將網頁的每個介面都視為平面設計,