---
# System prepended metadata

title: storybook

---

# storybook 

### intro
- storybook解決了甚麼: codebase長大時共用的 component變得很chaotic，storybook提供一個元件型錄 for 展示/紀錄/測試 元件的 props & 不同的樣貌
    

![](https://i.imgur.com/QHlz5ny.png)


- 甚麼是story
    - 以JS角度: 一個function (A story is a function that describes how to render a component)
        - <b>Default export => component
        - named exports => the stories</b>
    - 以storybook介面角度: 一個component (A story is a component with a set of arguments that define how the component should render)


    
- jest 測試: 除了提供一個介面整理元件外，storybook的另一個好處是有一個類似sandbox的地方可以測試元件 [待研究](https://storybook.js.org/docs/react/writing-tests/introduction)

### write a sotry (step by step)
#### 
#### 0. 建立環境設定檔


```javascript=
module.exports = {
  stories: ['../src/**/*.stories.js'], // 入口，所有stories檔案都在這個目錄下
  addons: [                            // storybook的擴充功能 類似套件
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  webpackFinal: async (config, { configType }) => {
    // 設定 path alias
    config.resolve.alias = {
      ...config.resolve.alias,         
      '@components': path.resolve(__dirname, '../src/components'),
      '@module': path.resolve(__dirname, '../src/module'),
      '@assets': path.resolve(__dirname, '../src/assets'),
      '@utils': path.resolve(__dirname, '../src/utils'),
    };

    config.module.rules.push({
      test: /\.(scss)$/,
      use: [
        {
          loader: 'style-loader',
        },
        {
          loader: 'css-loader',
        },
        {
          loader: require.resolve('sass-loader'),
          options: {
            implementation: require('sass'),

            // 設定全域CSS檔案 (或環境設定檔也可用這個方法+)
            // 詳https://webpack.js.org/loaders/sass-loader/#additionaldata

            additionalData: ` @use "src/assets/scss/utils/variables";
            @use "src/assets/scss/utils/mixin";`,
          },
        },
      ],
      include: path.resolve(__dirname, '../'),
    });
    return config;
  },
  framework: '@storybook/react',
  core: {
    builder: '@storybook/builder-webpack5',
  },
};


```


不贅述細節，詳情請洽 `/packages/ui/.storybook` OR [storybook文件](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) OR 再視需求看webpack文件



#### 1. 依設定檔的入口路徑建立檔案 & import component & 他的scss檔案
檔名要以 `*.stories.@(j|t)sx?.` 結尾 (除非有自定義設定檔)
For example, "Badge.stories.js" or "Badge.stories.tsx"


#### 2. default export (定義元件)
- title: 這個變數會影響storybook的目錄架構，可以用來group components

    `ROOTS(全大寫) / Components / Buttons`
    `ROOTS(全大寫) / Components / ActivityTitle`
    
    => storybook官方放建議 stories 的架構要跟專案的架構一樣
        => [可以自動產生title](https://storybook.js.org/docs/react/configure/overview#configure-story-loading)
    => [當component只有一個story的時候，也是由title控制，避免多包一層](#情境d-元件只有一個story-gt-name-hoisting)

- *component: 主角
- <b>args</b>: 物件object，會出現在storybook的控制介面，可以控制的參數/props, 可以在三種層級定義:
    - Story args [最常用的情況](#3-named-export-定義元件的stories): 寫在 <b>name export</b>
    - component args: <b>寫在default export</b>
        - argTypes: 設定參數(如control, description, defaultValue etc)
        - args: 賦值

        <b>可以把argTypes想像成propTypes, args想像成props</b>
        
    - global args (在preview.js export args object)，使用情境可能是環境變數，如之後要分開首頁&路標頁的元件可能就可以在那邊定義

        ```
        // preview.js

        // 所有stories都要有一個theme的props
        export const argTypes = { theme: { control: 'select', options: ['homepage', 'sign'] } };

        // 設定theme的預設值為light
        export const args = { theme: 'homepage' };
        ```

#### 3. named export (定義元件的stories)
- bind => 複製一份template (JS 的 this 會指向剛剛assign的const)
```
💡 Template.bind({}) is a standard JavaScript technique for making a copy of a function. 
We copy the Template so each exported story can set its own properties on it.
```
- args => react 元件的props


#### <span style="color:blue">< RECAP step 1~3 > </span>
    
1. 建立檔案在 main.js 指定的entry目錄
2. default export 一個object
    ```
    {
        title: Components/Button
        component: Button, 
    }
    ```
3. 很多個named export => 元件的故事
    ```
    // 3-1. 建立一個Template元件 等等可以複製用的
    const Template = (args) => <Button {...args} />;
    
    // 3-2. 複製 Template
    export const ButtonLogin = Template.bind({});
    
    // 3-3. 寫 Story args (上面提到寫arg的方法有三種)
    ButtonLogin.args = {
      text: '登入',
      btnType: 'a',
      btnSize: 'sm',
      btnShape: 'oval',
      btnColor: 'tertiary',
    };
    ```



#### 4. [proptypes](https://github.com/facebook/prop-types)寫好會自動設定好storybook的[argstable](https://storybook.js.org/docs/react/writing-docs/doc-block-argstable) (storybook的args控制介面)
  * 多用 oneOf, shape...之類的去定義寫死的enum type
  * instead of `PropTypes.string`, use `PropTypes.oneOf(['homepage', 'sign'])`

#### 5. 依需求去調整

##### 情境a. prop加上 description
##### 情境b. 改寫story名稱
##### 情境c. 新增props專門for storybook展示用
##### 情境d. 元件只有一個story => name hoisting
##### 情境e. [打造展示頁](https://storybook.js.org/docs/react/writing-stories/build-pages-with-storybook)
##### 情境f. [多個元件的故事](https://storybook.js.org/docs/react/writing-stories/stories-for-multiple-components)

