[storybook] Storybook 介紹與實作
說明
如果團隊要檢查各個成員之間更改了哪些程式碼,可以使用版本控制工具,例如:Git。
但今天如果是想要看到的是各個元件之間,或是畫面的變化,例如:按鈕樣式、頁面樣式等,就可以使用 Storybook,來講述你元件與畫面的故事。
Storybook 也支援 Markdown 語法,所以針對一些教學需要用文字來表達的話也可以。
在開始之前,可以先到官方的Showcase,體驗一下使用 Storybook 帶來的好處。
安裝
要使用 Storybook,必須先有一份 React 專案,這邊用 Vite 快速建立一份 React 專案。
npm create vite@latest 你的專案名稱 -- --template react && npm install
接著在該專案目錄底下安裝 Storybook:
npx sb init --builder @storybook/builder-vite
安裝的時間會有點久是正常的
安裝成功後會看到專案多了 .storybook
資料夾,在 src 裡面也可以看到 stories
資料夾,裡面已經有預設的 stories 了,執行以下指令來開啟 Storybook:
npm run storybook
建立 stories
現在可以建立我們的第一個 stories,在 stories 資料夾內新增一個名為 Box.jsx
和 Box.stories.jsx
的檔案。
import React from "react";const Box = () => { const style = { width: "200px", backgroundColor: "cyan", }; return <div style={style}>Box</div>;};export default Box;
import Box from "./Box";export default { title: "Box", component: Box,};export const largeBox = () => <Box />;
現在在畫面上可以看到 Box 底下有一個 Large Box,也就是剛剛建立的 stories。
如果日後 stories 太多需要分類的話,只需要在 title 的地方做更改即可。
import Box from "./Box";export default { //將Box分類到Component title: "Component/Box", component: Box,};export const largeBox = () => <Box />;
PropTypes
如果要讓使用者能夠和 Component 互動的話,需要去定義 Component 的 PropTypes。
import React from "react";import PropTypes from "prop-types";const Box = () => { const style = { width: "200px", backgroundColor: "cyan", }; return <div style={style}>Box</div>;};export default Box;Box.propTypes = { backgroundColor: PropTypes.string, label: PropTypes.string, size: PropTypes.oneOf(["sm", "md", "lg"]), show: PropTypes.bool,};
重新整理後就會在 stories 的 control 中看到我們定義的 prop 。
那要怎麼讓 Box Component 接收到這些 prop 呢?
一旦我們設定好 PropTypes 後,Box Component 其實就已經能接收到這些 prop 了。
import React from "react";import PropTypes from "prop-types";const Box = ({ backgroundColor, label, size, show }) => { console.log(backgroundColor, label, size, show); const style = { width: "200px", backgroundColor: "cyan", }; return <div style={style}>Box</div>;};export default Box;Box.propTypes = { backgroundColor: PropTypes.string, label: PropTypes.string, size: PropTypes.oneOf(["sm", "md", "lg"]), show: PropTypes.bool,};
打開 console 後,會發現只有label
有值,其他的都是 undefined。
這時候我們可以在 controls 的地方去選取 backgroundColor,size 的部分選擇 sm,show 的部分選擇 true。
再看一次 console,就會看到我們的 prop 有成功輸出了。
所以現在可以將傳入的參數帶進 Component 裡面並做一些判斷。
import React from "react";import PropTypes from "prop-types";const Box = ({ backgroundColor, label, size, show }) => { let scale = 1; if (size == "sm") scale = 0.5; if (size == "md") scale = 0.75; const style = { width: "200px", padding: `${scale * 0.5}rem ${scale * 1}rem`, backgroundColor, }; return <>{show && <div style={style}>{label}</div>}</>;};export default Box;Box.propTypes = { backgroundColor: PropTypes.string, label: PropTypes.string, size: PropTypes.oneOf(["sm", "md", "lg"]), show: PropTypes.bool,};
Default Props
如果我們希望 Component 在剛載入的時候就有預設 prop 的話,可以在 Component 中定義 defaultProps
。
import React from "react";import PropTypes from "prop-types";const Box = ({ backgroundColor, label, size, show }) => { let scale = 1; if (size == "sm") scale = 0.5; if (size == "md") scale = 0.75; const style = { width: "200px", padding: `${scale * 0.5}rem ${scale * 1}rem`, backgroundColor, }; return <>{show && <div style={style}>{label}</div>}</>;};export default Box;Box.propTypes = { backgroundColor: PropTypes.string, label: PropTypes.string, size: PropTypes.oneOf(["sm", "md", "lg"]), show: PropTypes.bool,};Box.defaultProps = { backgroundColor: "cyan", label: "Hello I'm Box.", show: true, size: "lg",};
建立多份 stories
現在可以建立基於 Box Component 的其他 stories,例如:尺寸較小的 Box(sm)、尺寸中型的 Box(md)。
我們可以先創建一個樣板(Template),樣板會接收到參數,之後將該參數傳至 Box Component,以此方式建立多份 story。
💡 Template.bind({}) 是 標準的 JavaScript 技巧,用來複製函式。此技巧是用來讓每個輸出的 story 可以設定各自的參數,但使用完全相同的方法。
來源:https://storybook.js.org/tutorials/intro-to-storybook/react/zh-TW/simple-component/
import Box from "./Box";export default { title: "Component/Box", component: Box,};const Template = (args) => <Box {...args} />;export const largeBox = Template.bind({});largeBox.args = { label: "Large Box", size: "lg",};export const mediumBox = Template.bind({});mediumBox.args = { label: "Medium Box", size: "md",};export const smallBox = Template.bind({});smallBox.args = { label: "Small Box", size: "sm",};
Controls
我們也可以自訂 control 的 type,像是在 label prop 的地方,我們想要呈現的是下拉式選單,並且有值能夠讓使用者自行選取。
import Box from "./Box";export default { title: "Component/Box", component: Box, argTypes: { label: { options: ["選項1", "選項2", "選項3"], control: { type: "select" }, }, },};const Template = (args) => <Box {...args} />;export const largeBox = Template.bind({});largeBox.args = { label: "Large Box", size: "lg",};export const mediumBox = Template.bind({});mediumBox.args = { label: "Medium Box", size: "md",};export const smallBox = Template.bind({});smallBox.args = { label: "Small Box", size: "sm",};
Decorators
如果希望在每一個 story 底下的 Component 都能夠套樣相同的 CSS,則可以使用 Decorators 來達到該功能。
先建立一個 Center 的 HOC,在裡面撰寫置中的 CSS:
import React from "react";const Center = ({ children }) => { return ( <div style={{ display: "flex", justifyContent: "center", alignItems: "center", }} > {children} </div> );};export default Center;
之後打開 .storybook
資料夾內的 preview.jsx
,直接 export decorators,decorators 內自帶參數 Story,可以把它想像成我們的所有 stories 底下的 Component。
所以現在是使用 Center HOC 將所有的 Component 包住,以此套用置中 CSS。
如果是使用 vite 開發的話,preview 檔案的結尾記得要改成.jsx
。
import Center from "../src/stories/Center";export const parameters = { actions: { argTypesRegex: "^on[A-Z].*" }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, },};export const decorators = [ (Story) => ( <Center> <Story /> </Center> ),];
Addon
我們也可以在 Storybook 專案內安裝額外的插件,這邊示範如何使用 addon-viewport
插件。
npm i @storybook/addon-viewport
接著打開 preview.jsx
,在 parameters
的地方加上 viewport,並把 addon-viewport 的 INITIAL_VIEWPORTS 給帶入。
import Center from "../src/stories/Center";import { INITIAL_VIEWPORTS } from "@storybook/addon-viewport";export const parameters = { actions: { argTypesRegex: "^on[A-Z].*" }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, viewport: { viewports: INITIAL_VIEWPORTS, },};export const decorators = [ (Story) => ( <Center> <Story /> </Center> ),];
回到畫面,在上方的 size of preview 多了更多的尺寸讓我們去做選擇,方便進行測試。
Chromatic
# Workflow name
name: "Chromatic Deployment"
# Event for the workflow
on: push
# List of jobs
jobs:
test:
# Operating System
runs-on: ubuntu-latest
# Job steps
steps:
- uses: actions/checkout@v1
- run: yarn
#👇 Adds Chromatic as a step in the workflow
- uses: chromaui/action@v1
# Options required for Chromatic's GitHub Action
with:
#👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/react/en/deploy/ to obtain it
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}