【Storybook】vue3 + Storybook7超かんたん環境構築 & Storybook基礎解説

## はじめに

Storybook7がリリースされたので、Vue3環境でセットアップをやってみたところ、
とても簡単にセットアップができたので、Vue3環境をベースとしたStorybookの基礎的な使い方について解説します。
Vue3環境の環境構築に少し触れつつ、Storybookの環境構築と、Storybookの構成・記述方法をサンプルファイルを用いて簡単に解説します。
- **Vue3を使い始めたから一緒にStorybookも導入してみたい**
- **Storybookどんな雰囲気か軽く動かしてみたい**

など「Storybookに興味があるけど触った事がない」という方々のご参考になれば幸いです。

## 構成
- vue v3.3.4
- storybook v7.0.24

## VueセットアップからStorybook起動までの手順
### 1. Vueをセットアップ
最新版でセットアップ
```
npm init vue@latest
```
オプションの選択（ご自身のプロジェクトに合わせて設定してください）
```
? Project name: 任意
? Add TypeScript? › Yes
? Add JSX Support? › No
? Add Vue Router for Single Page Application development? › Yes
? Add Pinia for state management? Yes
? Add Vitest for Unit Testing? Yes
? Add an End-to-End Testing Solution? Playwright
? Add ESLint for code quality? Yes
? Add Prettier for code formatting? Yes
```
パッケージインストール & vue起動する
以下コマンドを実行
```
npm install
npm run dev
```

### ２. Storybookをセットアップ
最新版でセットアップ
各アドオンなどのパッケージ単体でインストールすると少し手間なので、
Vueと同様に、必要パッケージのインストールとセットアップを一緒に行います。

以下コマンドを実行
```
npx storybook@latest init
```
以下の質問はとりあえずyesで進める
```
Ok to proceed? (y) y
Would you like to install it? y
```
必要なパッケージ、タスクランナーも定義済みになっている。
（Vue&Vite環境だと、なぜわかった？）
```package.json
"scripts": {
  "storybook": "storybook dev -p 6006",
  "build-storybook": "storybook build"
 },
"devDependencies": {
  "@storybook/addon-essentials": "^7.0.24",
  "@storybook/addon-interactions": "^7.0.24",
  "@storybook/addon-links": "^7.0.24",
  "@storybook/blocks": "^7.0.24",
  "@storybook/testing-library": "^0.0.14-next.2",
  "@storybook/vue3": "^7.0.24",
  "@storybook/vue3-vite": "^7.0.24",
  "storybook": "^7.0.24",
}
```
必要な設定ファイルも用意されている
```.storybook/main.ts
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions'
  ],
  framework: {
    name: '@storybook/vue3-vite',
    options: {}
  },
  docs: {
    autodocs: 'tag'
  }
}
export default config
```
```.storybook/preview.ts
import type { Preview } from '@storybook/vue3'

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/
      }
    }
  }
}

export default preview
```
### 3. Storybookを起動
以下コマンドを実行
```
npm run storybook
```
無事起動できました！
![スクリーンショット 2023-07-03 19.04.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/ac1e8ae4-b3cb-78da-f5a1-939d3176b3ae.png)
「src/stories」ディレクトリが生成され、Button、Header、Pageなどのサンプルファイルが出来上がっているのでプロジェクトに不要であれば削除してください。
「src/components」などの別ディレクトリに移動しても問題ありません。

次に、このサンプルファイルを使ってstoryを定義しているファイルの中身について解説していきます。

## storyとは？

先程storiesディレクトリに生成された「Button.vue」を使って、storyを定義している「Button.stories.ts」のファイルの中身について解説します。
まずButton.vueの中身をご覧ください。
### Button.stories.vue
※ 少し整形しています。
```src/stories/Button.vue
<script lang="ts" setup>
import { computed } from 'vue';

const props = withDefaults(defineProps<{
  label: string,
  primary?: boolean,
  size?: 'small' | 'medium' | 'large',
  backgroundColor?: string,
}>(), { primary: false });

const emit = defineEmits<{
  (e: 'click', id: number): void;
}>();

const classes = computed(() => ({
  'storybook-button': true,
  'storybook-button--primary': props.primary,
  'storybook-button--secondary': !props.primary,
  [`storybook-button--${props.size || 'medium'}`]: true,
}));

const style = computed(() => ({
  backgroundColor: props.backgroundColor
}));

const onClick = () => {
  emit("click", 1)
};
</script>

<template>
  <button type="button" :class="classes" @click="onClick" :style="style">{{ label }} </button>
</template>

<style scoped>
※ 省略
</style>
```
Vue&tsのコードを解説は割愛しますが、Button.vueのpropsの変化によって、ボタンがどのように変化するかを簡単にご説明します。
- label
  - ボタンの中のテキスト
- primary
  - tureの場合、青ボタン。falseの場合、白抜きのボタン。
- size
  - 'large' or 'medium' or 'small'のいずれかをセットしてボタンの大きさを設定する
- backgroundColor
  - 任意のカラーコードをセットして、ボタンの背景色を設定する。

### Button.stories.ts
自動生成されたstory定義用ファイル「Button.stories.ts」を見てみます。
storyを新規作成する場合は、任意のファイル名で「**xxx.stories.ts**」としてください。
※ 「**xxx.stories.js**」でも問題ありません。
※ コメントなどを削除して、少し整形しています。
```src/stories/Button.stories.ts
import type { Meta, StoryObj } from '@storybook/vue3';
import Button from './Button.vue';

type Story = StoryObj<typeof meta>;

const meta = {
  title: 'Example/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    size: { control: 'select', options: ['small', 'medium', 'large'] },
    backgroundColor: { control: 'color' },
    onClick: { action: 'clicked' },
  },
  args: { primary: false }
} satisfies Meta<typeof Button>;

export default meta;

export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
  },
};
export const Secondary: Story = {
  args: {
    primary: false,
    label: 'Button',
  },
};
export const Large: Story = {
  args: {
    label: 'Button',
    size: 'large',
  },
};
export const Small: Story = {
  args: {
    label: 'Button',
    size: 'small',
  },
};
```
初めてご覧になる方は、イマイチ仕組みがよくわからないと思いますので、
（私が初めて見た時は、これで何が起こるのか、全くわかりませんでした）
細かく解説していきます。

### Meta設定
##### title
`title: 'Example/Button',`
storyのタイトルを設定します。
titleを「/」で区切って画面の左メニューで階層構造を自動で表現することができます。
![スクリーンショット 2023-07-03 20.30.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/32cc6359-bb47-5385-41ab-559efdf9c2ae.png)
##### component
`component: Button,`
importしたファイルをセットします。
`import Button from './Button.vue';`
##### tags
`tags: ['autodocs'],`
と定義すると、自動で**ドキュメントページ**を生成します。
![スクリーンショット 2023-07-03 20.34.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/0bc295d0-a0e5-510f-cac5-430db19c43d4.png)
このドキュメントページがとても便利で、
propの値を操作することで、画面上でpropsの値の変化によるUIを変更を確認することができます。
（**初めて使った時はすごく感動しました！**）

※ 補足
「.storybook/main.ts」のStorybookConfigで`docs: {autodocs: true}`を設定済みの場合は、`tags: ['autodocs'],`は**不要**です。
```
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
  docs: {
    autodocs: true
    defaultName: 'Documentation' // ドキュメントページの名称を「docs」から変更できます
  }
}
export default config
```

##### argTypes
```
  argTypes: {
    size: { control: 'select', options: ['small', 'medium', 'large'] },
    backgroundColor: { control: 'color' },
    onClick: { action: 'clicked' },
  },
```
argTypesは引数の型を設定する項目ですがvueの場合、各propsの型を定義します。
ドキュメントページ中段のエリアの...
![スクリーンショット 2023-07-03 20.52.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/6e64c071-800b-7adf-e5f2-e9995158b372.png)
このエリアになります。「Control」列は、画面上で変更することができます。
例えば...
- primaryを「false」
![スクリーンショット 2023-07-03 20.55.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/34ed69b6-c682-c655-f26d-0f4c3e0bb14f.png)
- primaryを「true」
![スクリーンショット 2023-07-03 20.55.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/f4465c3d-26bb-86b5-75d6-eca3e841d30d.png)

このようにControlの値を変更し、UIの仕様・動作確認などを画面上で行うことができます。

また、argTypesは**自動推論**機能が備わっているため、argTypesを一切定義しなくてもドキュメントに反映されます。
controlプロパティは種類が豊富なので、自動推論で決めたれた型ではなく、これらを手動で設定した場合は、argTypesを定義することをおすすめします。
※ argTypesを手動で定義する場合は、以下をご参照ください。

https://storybook.js.org/docs/react/api/arg-types

##### args
`args: { primary: false },`
先程のargTypesは引数の型を定義するものですが、argsは引数の値を定義することができます。
primaryをfalseにしていますが、初級値をtrueにすることもできます。

### UIの状態別にstoryを表現する
`Button.stories.ts`以下の構文をご覧ください。
```
export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
  },
};
```
初めて見る方には、これを定義することでどのようになるか、わかりずらいかもしれませんが、
画面を見てみると、
![スクリーンショット 2023-07-04 21.19.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/59207edf-a95c-cb56-4cc0-0257bcf69703.png)
exportしたPrimary変数のページが生成されています。
このように、Buttonの引数によって、どのように状態（見た目）が変化するかを個別に表現することできます。

一例として、`Button.stories.ts`に以下のコードを追加してみます。
```
export const hoge = {
};
```
画面を更新すると、左メニューに「Hoge」ページが生成されています。
![スクリーンショット 2023-07-04 21.23.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/e7f35f8d-e9fd-94d7-8530-dd7bfc000e21.png)
次に、Primary、Secondaryと同様に、**args**を追加してみます。
```
export const hoge = {
  args: {
    label: 'ボタン',
    size: 'small',
    backgroundColor: '#000'
  },
};
```
画面を確認すると、
![スクリーンショット 2023-07-04 21.25.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182447/1b428ab2-f80a-a313-fa9d-f21633a930e9.png)
hoge変数で定義した黒いボタンが表現されています。
この仕組みを利用して、コンポーネントのUIを状態変化を画面で確認することができます。

## さいごに
私がStorybookを利用して、とても役にたったケースとして、
- プロジェクトに新規参画した時に、どんなコンポーネントが存在しているか、また、どのように管理しているか、Storybookがない現場ではソースコードを見ながらアプリケーション全体を操作して確認していましたが（時間と手間がかかる）Storybookを見れば、これをすぐに把握することができた。
- フロントエンドエンジニア同士のコミニュケーションツールとして機能するだけではなく、プロジェクト内のデザイナーの方やエンジニア以外の方々とのコミニュケーションツールとしても活用することができた。
- コードレビュー、単体確認などの効率化。

など他にも様々な場面でフロントエンドエンジニアの業務効率化に役立つと考えています。
今回ご紹介できませんでしたが、Storybookではビジュアルリグレッションテストやインタラクションテストなど、他にも様々な活用方法が存在します。

この記事見て、Storybookに少しでも興味を持っていただいて、皆様のプロジェクトの発展にお役立ていただければ幸いです。