方格精選

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

更新於 2024/03/06閱讀時間約 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
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
问我擅长做什么,现在想想,真的没有很擅长的事。小时候,比起很多同辈分的学生,读书成绩还不错,不过也不是超级厉害,名列前茅。出社会后,厉害的人比比皆是,更加不觉得自己有多厉害了。真要绞尽脑子想的话,可能是以下几点吧!
“身”指的是八字中的日干。 “旺”意思是有力量,有能量; “弱”意思是力量小,能量少; 八字中有木火土金水五行,身旺指的是:八字中日干所代表的五行比其他五行相对有力量;身弱反之。这个定义还不够严谨,更准确的说是日元的生助来源强有力。对新手来说差不多,有的东西只能意会不好言传。得多思考。 五行是个生态
这个世界任何事任何物都有个主要启动点,有的事物主要启动点明朗可见,如电器要靠电启动、走路要靠脚启动等,而有的事物主要启动点混沌摇摆难辨,如树木生长主要靠土启动还是水?又如人的认知主要靠什么启动呢? 问到这句,我知道肯定有人会说是大脑,其实学校里的学生人人都有个大脑,为什么有的学生成绩好,而有的学生成
人生而言,简单的说格局是一种性格格式与认知结构及范围,它不是社会地位,也不是财富高度,但它却是创造社会地位与财富高度的必要条件,格局有多好,创造的社会地位与财富就有多高。只是要提醒的是请千万不要盲目的去追求格局,盲目的去追求格局高度,而不实实在在的去走自己的性格与认知路线,结果一定会得不偿失。 看了
Thumbnail
超级签证是为加拿大公民或者永久居民的父母、祖父母设置的。虽然目前父母、祖父母团聚移民处于停滞状态,但超级签证基本都能签发,多年多次有效,每次入境可以停留两年,基本上解决了加拿大公民、永久居民和父母分居两地的问题。除了旅游签证的基本要求,超级签证还需要: • 证明你的子女或孙子女达到最低收入门槛;
Thumbnail
超级签证是为加拿大公民或者永久居民的父母、祖父母设置的。虽然目前父母、祖父母团聚移民处于停滞状态,但超级签证基本都能签发,多年多次有效,每次入境可以停留两年,基本上解决了加拿大公民、永久居民和父母分居两地的问题。除了旅游签证的基本要求,超级签证还需要: • 证明你的子女或孙子女达到最低收入门槛;
原文連結: https://zb.house/一文读懂什么是去中心合约保险/ 【本文章轉載自鑄幣局 - 提供專業的加密貨幣行業的研究成果的分析平台。】 "绝大多数的DeFi协议都会将保险服务看作为协议服务的重要组成。唯有这样,才能真正解决用户的后顾之忧。" 去中心合约保险 NXM COVER
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
问我擅长做什么,现在想想,真的没有很擅长的事。小时候,比起很多同辈分的学生,读书成绩还不错,不过也不是超级厉害,名列前茅。出社会后,厉害的人比比皆是,更加不觉得自己有多厉害了。真要绞尽脑子想的话,可能是以下几点吧!
“身”指的是八字中的日干。 “旺”意思是有力量,有能量; “弱”意思是力量小,能量少; 八字中有木火土金水五行,身旺指的是:八字中日干所代表的五行比其他五行相对有力量;身弱反之。这个定义还不够严谨,更准确的说是日元的生助来源强有力。对新手来说差不多,有的东西只能意会不好言传。得多思考。 五行是个生态
这个世界任何事任何物都有个主要启动点,有的事物主要启动点明朗可见,如电器要靠电启动、走路要靠脚启动等,而有的事物主要启动点混沌摇摆难辨,如树木生长主要靠土启动还是水?又如人的认知主要靠什么启动呢? 问到这句,我知道肯定有人会说是大脑,其实学校里的学生人人都有个大脑,为什么有的学生成绩好,而有的学生成
人生而言,简单的说格局是一种性格格式与认知结构及范围,它不是社会地位,也不是财富高度,但它却是创造社会地位与财富高度的必要条件,格局有多好,创造的社会地位与财富就有多高。只是要提醒的是请千万不要盲目的去追求格局,盲目的去追求格局高度,而不实实在在的去走自己的性格与认知路线,结果一定会得不偿失。 看了
Thumbnail
超级签证是为加拿大公民或者永久居民的父母、祖父母设置的。虽然目前父母、祖父母团聚移民处于停滞状态,但超级签证基本都能签发,多年多次有效,每次入境可以停留两年,基本上解决了加拿大公民、永久居民和父母分居两地的问题。除了旅游签证的基本要求,超级签证还需要: • 证明你的子女或孙子女达到最低收入门槛;
Thumbnail
超级签证是为加拿大公民或者永久居民的父母、祖父母设置的。虽然目前父母、祖父母团聚移民处于停滞状态,但超级签证基本都能签发,多年多次有效,每次入境可以停留两年,基本上解决了加拿大公民、永久居民和父母分居两地的问题。除了旅游签证的基本要求,超级签证还需要: • 证明你的子女或孙子女达到最低收入门槛;
原文連結: https://zb.house/一文读懂什么是去中心合约保险/ 【本文章轉載自鑄幣局 - 提供專業的加密貨幣行業的研究成果的分析平台。】 "绝大多数的DeFi协议都会将保险服务看作为协议服务的重要组成。唯有这样,才能真正解决用户的后顾之忧。" 去中心合约保险 NXM COVER