それだけじゃない！ Storybook の活用方法

# はじめに
[なぜフロントエンド開発にStorybookを導入するのか](https://qiita.com/aki202/items/bd5a22813352d1834a93) というエントリーで、**コンポーネントの凝集度という切り口から Storybook の有用性について述べており**、素晴らしいと思いました。

Storybook に関しては他にも様々なエントリーがあり、主にデザイナーとのコミュニケーションツールや、カタログツールとして紹介されていることが多いように感じます。

一方で、Storybook には、**他にも知っておくべき魅力**があると思うので、ここにまとめていきます。

# Storybook とは
[Storybook](https://storybook.js.org/) は React や Vue.js 向けの開発サポートツールです。[先程紹介したエントリー](https://qiita.com/aki202/items/bd5a22813352d1834a93)では、Storybook の活用方法を、以下のように紹介していました。


> 1. エンジニアとデザイナーが協働する環境として使う
> 2. エンジニアがサンドボックス環境として使う
> 3. 再利用できるComponentのカタログとして使う


はい、**完全に同意です。**特に、3 について詳しく解説されていました。

このエントリーでは、2 の**サンドボックス環境としての活用方法**に加え、**コンポーネントのドキュメントとしての活用方法**にフォーカスしてみたいと思います。

# サンドボックス環境およびドキュメントとしての Storybook
今回は一つのコンポーネントを題材にして、Storybook を使った開発方法を解説します。最後に、**もし Storybook が無ければどれほど困るか、**ということを考えてみたいと思います。

## 開発環境
今回は **Vue.js + TypeScript** を使用してコンポーネントを書いていきます。

## コンポーネントの仕様
次のようなコンポーネントを作ってみます。

### UserInfoCard
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/65aa36fa-e28a-646b-f0c1-9d6e250f9fca.png)

- ユーザの ID と名前を表示するカード
- 管理者として閲覧するときはメールアドレスを見ることができる
- メールアドレスが無いときは[-]を表示する
- メールアドレスが長過ぎるときは[...]のように省略表示される

## STEP 1 : コンポーネントとストーリーのスケルトンを作成する
```UserInfoCard.vue
<template>
  <div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"

@Component
export default class UserInfoCard extends Vue {
}
</script>
```

```UserInfoCard.story.ts
import UserInfoCard from "./UserInfoCard.vue"

const ARG_TYPES = {
}

export default {
  title: "UserInfoCard",
  component: UserInfoCard,
  argTypes: ARG_TYPES,
}

export const basic = () => ({
  components: {
    UserInfoCard,
  },
  props: Object.keys(ARG_TYPES),
  template: `
    <UserInfoCard />
  `,
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/1078649a-7884-bc06-b075-70746ed1bb4c.png)

はい、これで開発の準備完了です！
今回は主に docs タブの画面を使用します。 

ちなみに、上記ファイルを VSCode のスニペットとして登録しておくと非常に便利です。

## STEP 2 : story ファイルを書く
私の場合、コンポーネントを書くときは**最初に story ファイルの方を書いてしまいます。**感覚としてはテスト駆動開発に近いです。**ストーリー駆動開発**と称して提唱していきたいです。

もちろん、テスト駆動開発と同じように、**最初に全てのストーリーを完全に書き切る必要はありません。**ある程度コンポーネントを書いてから、逐次書き足していっても OK です。

ここでのポイントは、**すべての仕様を確認できるようにコントローラを定義し、適切な形に変換してコンポーネントに渡すこと**です。

今回のコンポーネントは、

- 閲覧者の権限
- メールアドレスの長さ（無し/通常/長い）

というパラメータで表示が変化するので、コードは下記のようになります。

```UserInfoCard.story.ts
import { User } from "../models/user"
import UserInfoCard from "./UserInfoCard.vue"

const ARG_TYPES = {
  email: {
    control: {
      type: "select",
      options: {
        none: "",
        nomal: "hogehoge@example.com",
        long: "hogeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee@email.com",
      },
    },
    defaultValue: "hogehoge@example.com",
  },
  permission: {
    control: {
      type: "select",
      options: ["admin", "general", "guest"],
    },
  },
}

export default {
  title: "UserInfoCard",
  component: UserInfoCard,
  argTypes: ARG_TYPES,
}

export const basic = () => ({
  components: {
    UserInfoCard,
  },
  data: (vue: Vue) => {
    const { email } = vue.$props

    const user = new User(123, "user name", email)

    return {
      user,
    }
  },
  props: Object.keys(ARG_TYPES),
  template: `
    <UserInfoCard
      :user="user"
      :permission="permission"
    />
  `,
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/0522e7c6-26a1-2080-689f-348bc6024a27.png)

email から `none` `normal` `long` を選ぶと、それに対応する値 `hogehoge~` `hogeeeeeeee~` で `User` インスタンスが作成されます。
permission は選んだ値を直接 prop として渡しています。

今回は `select` コントローラを使いましたが、[公式ドキュメント](https://storybook.js.org/docs/vue/essentials/controls)で他のコントローラの使い方について詳しく解説されているので、是非参考にしてください。

#### コンポーネントの prop について
`UserInfoCard` の prop `user` と `permission` はまだ存在していませんが、このコンポーネントの描画に必要と思われる情報を**こういうインターフェースだったら良いな**という思いを込めて書いたものです。
ちなみに `User` はプロジェクトに既存のモデルクラスとして、次のようなものを想定しています。

``` user.ts
export class User {
  constructor(
    readonly id: number,
    readonly name: string,
    readonly email: string
  ) {}
}
```



## STEP 3 : コンポーネントを書く

ここまで書けば、後は、Storybook の画面を見ながらコンポーネントを作っていくだけです！
コントローラで prop の値を変えながら実装することで、仕様を満たしているかどうかをすぐに確認することができます。

```UserInfoCard.vue
<template>
  <div
    class="flex flex-col border border-solid border-gray-600 rounded-lg p-3"
    :style="{ width: '300px' }"
  >
    <div class="flex flex-row space-x-3">
      <div>ID:</div>
      <div>{{ user.id }}</div>
    </div>
    <div class="flex flex-row space-x-3">
      <div>name:</div>
      <div>{{ user.name }}</div>
    </div>
    <div
      v-if="permission === 'admin'"
      class="flex flex-row space-x-3"
    >
      <div>email:</div>
      <div class="truncate w-full">
        {{ user.email ? user.email : '-' }}
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { User } from "../models/user"
import { Component, Prop, Vue } from "vue-property-decorator"

@Component
export default class UserInfoCard extends Vue {

  /**
   * ログインしているユーザの権限
   *
   * `MyInformation` クラスの `permission` を使う
   */
  @Prop({ required: true })
  permission!: "admin" | "general" | "guest"

  /**
   * このカードに表示したいユーザ
   */
  @Prop({ required: true })
  user!: User
}
</script>
```

※ [tailwind](https://tailwindcss.com/) という CSS ライブラリを使用して記述しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/84c534f0-4a92-78f0-0775-fda38bc04e06.png)



コントローラの値を選択することで、表示の切り替わりを容易に確認することができます
##### permission が `general` または `guest`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/4c4c340f-df4e-0054-91da-069f22b82ebd.png)

##### permission が `admin` で email が `none`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/e3f2b4a6-da51-e340-8dcf-b323593942fe.png)

##### permission が `admin` で email が `long`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/d96397df-a61b-6edd-d055-ad8a983f7679.png)


## ドキュメントとしての側面
### description
Storybook の画面上に Description が表示されていたのに気づきましたか？
これは、`UserInfoCard.vue` で各 prop の上の行のコメントがそのまま反映されています。
他にも、event や control にも description を表示することができます。詳細は[公式ドキュメント](https://storybook.js.org/docs/vue/writing-docs/doc-blocks)をご参照ください。

**mark down 記法もサポートされていて、**非常に便利です。

### Show code
コンポーネントが表示されているエリアの右下の、`Show code` をクリックすると、story ファイルでどのようにコンポーネントを使っているかを見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/bd89caf3-3a19-fd3d-09a0-dbcd0b37a808.png)

これらの機能を活用することで、開発者は**コンポーネントファイルを見に行かなくても使い方や仕様を知ることができます。**

ここまでサンドボックス環境として使ってきた story ファイルは、**次の仕様追加や修正をする開発者がすぐに使える開発環境であり、コンポーネントのドキュメントにもなります。**

story ファイルを書く、という手間は発生しますが、それ以上の大きなリターンを得ることができるのです。


# Storybook がない世界
ここまで、Storybook をサンドボックス環境、そしてドキュメントとして利用する方法についてお伝えしてきました。

**もし Storybook が無ければ、どのようになるでしょうか？**

### プロダクトに新しい機能を追加する時
画面単位の大きめの新機能を実装することになったとします。開発初期段階で、小さなコンポーネント単位で実装を確認したい時、表示する画面がありません。確認するためには、**表示のための画面を一時的に作る**か、**ページ全体の完成を待つ**しか選択肢はありません。

### バグを修正する時
先程の `UserInfoCard.vue` で、「admin として閲覧している時、長いメールアドレスのユーザの表示が崩れる」というバグが発生したとします。再現するには、

1. ローカルでアプリケーションを立ち上げて、
2. admin としてログインし、
3. メールアドレスの長いユーザを作成し、表示を確認する

必要が有ります。
この状態からスタイルの修正をしていきますが、**ビルドに時間のかかるアプリケーションであれば、修正→確認 のサイクルに非常に時間がかかってしまいます。**

### 仕様を確認したい時
バグ修正や機能追加をするときは、もちろん現在の仕様を知る必要があります。仕様書があれば仕様書を見に行きますが、メンテナンスされていない場合は**実装を深く読む必要が有ります。**そもそも仕様書が存在していない場合はもっと悲惨です。


# まとめ
安心してください。Storybook はあります。

- 開発フェーズのどの段階でもコンポーネントの実装を確認でき、
- ビルドも早く、
- そのまま仕様書として残すことができる

素晴らしいツールです。使い始めて半年程度経ちましたが、もう手放すことはできません。

みなさんも、**ストーリー駆動開発**で、良いフロントエンドライフを送りましょう！