Storybook | 用 addon-controls 打造更好的 storybook 體驗

取代 addon-knobs 的官方插件

Photo by Marvin Meyer on Unsplash

前言

在 2020 年以前使用 storybook 想要動態地操作 component 的 props 通常都是使用 @storybook/addon-knobs 這個套件,而在 2020 作為它的替代品 @storybook/addon-controls 出現了。從下載數來看,短短一年之間,controls 的下載次數急起直追,到了寫這篇文章的時間點 2021 年 6 月 controls 的下載次數已經高於 knobs。

https://www.npmtrends.com/@storybook/addon-knobs-vs-@storybook/addon-controls

之所以 controls 的下載會超越 knobs 的原因有很多個,包括官方直接認列該套件於 essential addons 中,而 essential addons 是在初始化 storybook 時 npx sb init 就會被包含在預設安裝的套件底下。

如果你是第一次知道 essential addons,簡單來說就是 VIP 包的概念,一開始就給你很多工具,讓你打怪升級不用煩惱。你可以不用煩惱要裝哪些 addons,還要使用哪些工具才能提升 storybook 的 UX。

筆者認為 controls 的下載次數漸漸追上 knobs 的主要原因是「controls 寫起來就是比較爽」,用過 knobs 的使用方式有點彆扭,需要設定 decorators ,還需要在一開始 import 許多 props 的封裝函式,像是 textbooleannumber ,使用起來會需要多些工, component 的 story 從原始碼來看也會更不好讀,多加了很多料的感覺。

Controls 的寫法可以讓我們擺脫使用 knobs 的繁瑣感,讓我們可以更專注在撰寫 story 上,其他工程是在閱讀 story 的原始碼時也可以更加容易暸解 component 要怎麼使用。

接下來就讓我們來一瞥 Storybook Controls 的面容吧!

初始化專案與 storybook

用 CRA 初始化專案

在一開始,我們用 CRA (Create React App) 建立一個 typescript 的專案,當專案使用的 typescript 或是 prop-type 時,對於 controls 有很大的益處,因為 controls 可以抽離出 component 的 props type,為我們自動建立可操作的 props 跟類文件自動化,後面會再提到這件事。

npx create-react-app my-app --template typescript

初始化 storybook

專案建立後,我們再用以下指令初始化 storybook 的環境,這個指令其實挺用心的 😃 為什麼這樣說呢?如果是使用 storybook 的新手,由於它除了會幫我們安裝 storybook 的相關套件之外,還會自動安裝幾款 addons,不用再費盡心思搜尋需要的 addons。此外,它甚至還提供了一些 story 的範例,讓我們可以從中學習該如何用最新的語法撰寫 story。

npx sb init

在初始化 storybook 結束後,就可以輸入以下指令進入 storybook 的環境囉!

yarn storybook

以下是 storybook 初始化完畢後提供的範例 story,包含了三個 component 的 story,光是從這幾個範例就可以學習到很多東西,足以了解該怎麼基本的使用 storybook。

除了 *.stories.tsx 之外,另外還提供了 introduction.mdx 讓我們學習很潮的 MDX 格式,可以把 JSX 跟 Markdown 全部寫在一起,看過 MDX 的人都說酷 😆

安裝 addon controls

如果你是近期加入 storybook 的行列,可能在 package.json中就可以看到 @storybook/addon-essentials 的身影,這表示你的專案中已經包含 controls 了。如果沒有的話可以考慮安裝 essentials,如同上文他就像是 VIP 包,已經什麼都給你了。當然,如果不想在專案中裝很多套件,也可以考慮單獨安裝 controls:

yarn add -D @storybook/addon-controls

別忘了,在安裝完後要記得在 .storybook/main.js 裡面加上以下內容:

module.exports = {
addons: ['@storybook/addon-controls'],
};

但假設各位讀者都使用了 npx sb init 的指令,而這個指令已經幫我們都設定好 addons 了,所以也不用再自己安裝跟設定 controls,接下來我們就直接開始使用囉!

第一次使用 addon controls

為了使用 controls,其中一個重要的關鍵就是 Story.args ,主要就是透過從外部傳入 props 到 story 內部,controls 會擷取 props 的訊息,為我們建立起可操作的 control panel。

現在大致上可以歸類 Story 成兩種寫法,第一種是像我們平常在使用 component 時的寫法,如下面的 Primary story,如上面提到,因為要建立 controls,所已透過 Primary.args 傳入 props。

如果你遇到的情境是可以大量覆用的 presentation component,也可以考慮使用 Template.bind({}) 這種寫法,如下面的 Secondary component,與第一種寫法相同,使用 controls 時也是需要用 Secondary.args 傳入 props。

接下來,你可以打開 storybook,從下方看到 Controls 的分頁,裡面寫的是 <Button /> 的 props,每個 props 都有對應的操作方式,可以方便我們把玩這個 story。

看到這裡你有沒有發現,下方圖中打開的是 Primary story,而 Primary.args 只有傳入 primarylabel 而已,為什麼其他的 props 都一起出現了,是不是很神奇。

這是因為在 export default 裡面有加上 component 的參數,在 storybook 的 Component Story Format 中有提到這個參數的用途,它可以讓 storybook 獲得 component 的 metadata。雖然 component 是一個可以選擇不填的參數,但平常還是推薦必填這個參數變成團隊的 convention,方便在使用像是 Controls 這類的套件時可以幫助我們少寫很多程式碼。

Storybook 是怎麼拿到 props 的資料的

應該有些人會很好奇 storybook 是怎麼知道在 <Button /> 可以傳入哪些 props 的,之所以能做到這件事要歸功於 react-docgen-typescript 這個套件,storybook 就是透過它來拿到 component 的 props 型別的。

你可以在 Button.stories.tsx 中試試看在 Primary 中加入一段程式碼:

然後再打開 console 看看 Button.__docgenInfo.props 究竟會顯示什麼,你會發現這個物件裡面包含的正是 Button 這個 component 的 metadata,而 controls 便是用這樣的方式來自動萃取出 component 的 props 並顯示選項在頁面上的。

可以再做一點點嘗試,我們把 export defaultcomponent 參數拿掉,看看會發生什麼事情。不賣關子,你會發現 controls 剩下兩個參數,其他的 props 像是 backgroundColorsizeonClick 都不見了。

所以為了讓 storybook 可以幫我們萃取出 component 的 props

一定要記得在 export default 中加上 component

為 args 選擇 control type

取自官方文件

因為 storybook 很聰明,它可以根據 props 的 type 建立不同的操作元素,像是 radio、text、boolean 選擇器等等。可是有時候我們希望可以自定義操作 props 的方式,像是 Button 顯示「Text」跟「Long long text」的寬度應該會不一樣,這時如果想改用 radio 顯示顯示的內容選項該怎麼做呢?

第一種做法是可以在 export default 修改 argTypes 的參數,達到讓所有的 Button story 可以讀取到共同的設定,如此一來 Primary、Secondary、Large、Small 的 label 都變成了 radio。

label 變成了 radio

但有時候我們不想要讓所有的 story 都用同樣的 argTypes 設定 ,所以有第二種作作法是單獨對一個 story 設定 argTypes 。如下方的範例,設定 Primary story 的 label 是 radio 的選項,而其他的 story 中的 label 都還是要輸入文字。

最後,我們整理一下 argTypes 的結構大致上是這個樣子:

control 裡面還有一個 [option: string] ,如果不知道要填什麼,可以參考上方的表格的 Options,舉一個例子,當 typerange 時,便可以填入 maxminstep 的選項。

讓 controls type 自動匹配 props 命名

如果說每次都要為 story 寫 argType ,如果有幾十個、幾百個,甚至幾千個 story 時,我們不會想要逐一地為它們都加上 argType ,所以我們可以在 preview.js 裡面加上 regular express 自動為 props 匹配 control type。

例如下方的範例,只要匹配到 /background|color$/i 就可以自動轉換操作該 props 的方式為 { type: 'color' } ,而 color type 對應的是 color picker。

你可以看到預設 Button 的 story,裡面的 backgroundColor 就自動被轉換成了 color picker。

有趣的是 npx sb init 後產生的 Button.stories.tsx 裡面有一段 code 如下,它另外加上了 argTypes ,但其實前面在 preview.js 裡面定義的 matchers 就已經讓 backgroundColor 是 color picker 了,所以儘管把 argTypes 刪掉,原本的 control 也不會因此而變動。

在 control panel 中描述 props

接著,我們來看一個讓 control panel 變得更像是文件的功能,你可以在 preview.js 加上以下這段設定,就可以讓 props 多了 description 跟 default 的欄位,可以讓其他工程師更清楚這個 story 或是 component 可以帶入什麼樣的 props

加入這個設定以後神奇的事情發生了,Description 裡面自動加入了很多描述,但是剛剛根本沒有在 argTypes 裡面加上任何設定啊!為什麼他會知道 props 的描述文字是什麼?

你可以看到 Button.tsx 裡面的型別定義都加上了註解,嘗試修改註解,control panel 裡面的 props 描述也會跟著一起改變,是不是很讚呢!

為了讓 control 有這個功能,我們勢必得在定義 type 時加上一段註解,從另一個角度想 storybook 其實也是在鼓勵我們平時就要做好管理程式碼的工作,不僅可以增加可讀性,在維護時也可以減少通靈一段程式碼時間 XD

說到這邊,如果想在 story 裡面另外修改 props 的描述怎麼辦,因為有時候一個 props 並不會真正傳入到 component 裡面,而是為了展示 story 而定義的 props ,這時候我們可以這樣做:

如此一來,就可以看到 backgroundColor 這個的 description 被改變了。

最後…

看完了 storybook 的 addon controls 的介紹是不是讓你躍躍欲試,如果原本還在用 knobs 的工程團隊應該會想試試看這個工具吧!

這個工具可以幫助團隊在寫 story 時更加解省時間,還可以增加 story 程式碼的可讀性,甚至搭配最後介紹讓 controls panel 變成有點像文件的功能,額外還有讓整個 codebase 變得越來越好的優點,在等什麼,裝起來用用看吧!

--

--

一群技術人想要寫出一些好文章所建立的技術專欄。每週二一篇原創文章、一封電子報,歡迎大家訂閱!主網站: https://weekly.starbugs.dev/。

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Airwaves

Airwaves

每天進步一點點,在終點遇見更好的自己。