10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Storybookを触ってみたらとても良かった

Posted at

概要

Atomic Designを用いたフロントエンドの開発を行う中で、
「このコンポーネントって既にあるんだっけ?」と思い、プロジェクトの中を探し回ることがありました。
また、それっぽいファイルを見つけても、このコンポーネントはブラウザ上でどのように表示されるのかはぱっと見わかりません。

現状では、Vue.js devtoolsなどを使ってコンポーネント名を特定して、
実際の.vueまたは.tsxファイル名を照らし合わせる必要があります。

そんな中、Storybookがいいという噂を見かけたので、早速試してみました。

作業環境

  • Vue.js・・・3.2.45
  • storybook/vue3・・・6.5.15

etc..

Storybookとは

UIコンポーネントとページを分離して構築するためのフロントエンドワークショップです。
今までは、コンポーネントの挙動を確認するには、アプリ全体を起動、ページを移動、UIを正しい状態にする必要がありました。これには時間がかかるため、開発速度の低下につながります。

しかし、Storybookを使用することでこの問題を解決できます。具体的なメリットは以下に記載します。

メリット

  • コンポーネント単位でUI上での挙動を確認できる
  • コンポーネント(ここではatomなど小さな単位)とページを分離してユースケースを検証できる
  • UI上で到達しにくいエッジケースを検証できる
  • UIテストが容易である
  • コンポーネントやその状態をドキュメント化できる
  • UIの動作を共有できる

特に注目している点

私は特に、最後の2つのメリットに注目しています。

  • コンポーネントやその状態をドキュメント化できる
  • UIの動作を共有できる

理由は、これによってデザイナーとの認識齟齬の発生を防ぐことができたり、同一のコンポーネントを再び実装してしまうことを防ぐことができるからです。

私のチームではFigmaでデザインを行なっており、
Figmaでもコンポーネントを作成して、それらを組み合わせてページをデザインします。
その中で、デザインを確認しながらフロントで実装したコンポーネントの状態がデザイナーの意図に沿っているかどうかなど、相談ごとがどうしても増えてしまいます。
相手が忙しい場合も多いため、ここのコミュニケーションコストをできる限り下げる必要があります。

そこで、Storybookを利用することでコンポーネントやその状態をドキュメント化し共有することで、
アプリを操作して該当の画面に遷移する等の操作なく、コンポーネントを確認してもらうことが可能になります。
これによって、相談により発生するコミュニケーションコストが大幅に低下することが期待できます。

静的サイトとしてデプロイが可能なようなので、S3等にアップロードしておけば
誰でもいつでも確認できるドキュメントとして利用できそうです。

実際に使ってみる

導入

ここを参考にします。
Introduction to Storybook for Vue

ちなみに、以下のようにReactやSvelteなど各ライブラリに応じたチュートリアルが紹介されています。
親切すぎる・・・

まずはViteでプロジェクトを生成します。
Storybookは空のプロジェクトでは動かないため、既にフレームワークがセットアップされている必要があります。

$ yarn create vite
✔ Project name: … sample
✔ Select a framework: › Vue
✔ Select a variant: › TypeScript

$ cd sample
$ yarn
$ yarn dev
$ vite

  VITE v4.0.4  ready in 406 ms

  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help
✨  Done in 100.32s.

次に、用意したプロジェクトのルートディレクトリで以下のコマンドを実行し、Storybookをインストールします。

$ npx storybook init

中略

yarn install v1.22.19
[1/4] 🔍  Resolving packages...
success Already up-to-date.
✨  Done in 0.54s.
. ✓
🔎 checking possible migrations..

✅ migration check successfully ran


To run your Storybook, type:

   yarn storybook 

コマンド実行により以下のような変更が加えられます。

  • Storybookの実行とビルドに必要なスクリプトをセットアップする。
  • デフォルトのStorybookの設定を追加する。
  • 開始するためのデフォルトのストーリーを追加する。

プロジェクトにはstoriesディレクトリが追加されました。

package.jsonのscriptにはstorybookbuild-storybookが追加されています。

package.json
{
  "name": "sample",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },
  "dependencies": {
    "vue": "^3.2.45"
  },
  "devDependencies": {
    "@babel/core": "^7.20.12",
    "@storybook/addon-actions": "^6.5.15",
    "@storybook/addon-essentials": "^6.5.15",
    "@storybook/addon-interactions": "^6.5.15",
    "@storybook/addon-links": "^6.5.15",
    "@storybook/builder-vite": "^0.3.0",
    "@storybook/testing-library": "^0.0.13",
    "@storybook/vue3": "^6.5.15",
    "@vitejs/plugin-vue": "^4.0.0",
    "babel-loader": "^8.3.0",
    "typescript": "^4.9.3",
    "vite": "^4.0.0",
    "vue-loader": "^16.8.3",
    "vue-tsc": "^1.0.11"
  }
}

次に、以下のコマンドを実行してStorybookが正常に動作するかを確認します。

$ yarn storybook

yarn run v1.22.19
$ start-storybook -p 6006
info @storybook/vue3 v6.5.15
info 
info => Loading presets

info => Ignoring cached manager due to change in manager config
ℹ 「wdm」: wait until bundle finished: 
ℹ 「wdm」: Hash: 70239335d1b0f74c3dc2
Version: webpack 4.46.0

中略

Child HtmlWebpackCompiler:
                          Asset      Size               Chunks  Chunk Names
    __child-HtmlWebpackPlugin_0  6.32 KiB  HtmlWebpackPlugin_0  HtmlWebpackPlugin_0
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [./node_modules/html-webpack-plugin/lib/loader.js!./node_modules/@storybook/core-common/templates/index.ejs] 2.04 KiB {HtmlWebpackPlugin_0} [built]
ℹ 「wdm」: Compiled successfully.
╭──────────────────────────────────────────────────────╮
│                                                      │
│   Storybook 6.5.15 for Vue3 started                  │
│   9.08 s for manager and 1.07 s for preview          │
│                                                      │
│    Local:            http://localhost:6006/          │
│    On your network:  http://xxx.xxx.xxx.xxx:6006/    │
│                                                      │
╰──────────────────────────────────────────────────────╯

このように表示されたら成功しています。
ブラウザでhttp://localhost:6006/を開くと、以下の画面が表示されます。

Storyとは

ストーリーは、UIコンポーネントのレンダリング状態をキャプチャするために使用する、そのコンポーネントをどのようにレンダリングするかを記述した関数のことです。
コンポーネントごとに複数のストーリーを記述し、コンポーネントがサポートできるすべての状態を定義しておきます。

Storybookのインストール時に、CLIによってボタン、ヘッダー、ページのサンプルコンポーネントが作成されています。

各サンプルコンポーネントには、サポートする状態を示す一連のストーリーがあります。
UIでストーリーを参照し、.stories.jsまたは.stories.tsで終わるファイルでその背後にあるコードを見ることができます。ストーリーは、コンポーネントのサンプルを記述するためのES6モジュールベースの標準であるComponent Story Format (CSF)で記述されています。

Storyを書いてみる

自動生成されたボタンコンポーネントのストーリーを書いてみます。
既にButton.stories.jsが生成されているので、mdx形式を試します。

ちなみにmdxとは、markdownの中に、Reactで用いられるjsxを埋め込むことができるフォーマットです。

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

<script>
import './button.css';
import { reactive, computed } from 'vue';

export default {
  name: 'my-button',

  props: {
    label: {
      type: String,
      required: true,
    },
    primary: {
      type: Boolean,
      default: false,
    },
    size: {
      type: String,
      validator: function (value) {
        return ['small', 'medium', 'large'].indexOf(value) !== -1;
      },
    },
    backgroundColor: {
      type: String,
    },
  },

  emits: ['click'],

  setup(props, { emit }) {
    props = reactive(props);
    return {
      classes: computed(() => ({
        'storybook-button': true,
        'storybook-button--primary': props.primary,
        'storybook-button--secondary': !props.primary,
        [`storybook-button--${props.size || 'medium'}`]: true,
      })),
      style: computed(() => ({
        backgroundColor: props.backgroundColor,
      })),
      onClick() {
        emit('click');
      }
    }
  },
};
</script>

以下のように記述します。

Button.stories.mdx
import { Meta, Story } from '@storybook/addon-docs';
import Button from './Button.vue';

<Meta title="Button" component={Button} />

# Button

<Story name="Primary">
  {() => {
    return {
      components: { Button },
      template: `<Button primary label="Button" />`,
    };
  }}
</Story>

上記のStoryを追加することで、ブラウザでドキュメント化され確認することができます。
この時点ではボタンを押下しても何も起こりません。

次にargsを使用して、ボタンに対して引数を渡します。
ここで、
import { action } from "@storybook/addon-actions";
したうえでargsにonClick: action("clicked")のようにイベントに対するアクションを設定しています。

Button.stories.mdx
import { Meta, Story } from '@storybook/addon-docs';
import { action } from "@storybook/addon-actions";
import Button from './Button.vue';

<Meta title="Button" component={Button} />

# Button
export const Template = (args) => ({
  components: { Button },
  setup() {
    return { args };
  },
  template: '<Button v-bind="args" />',
});

<Story
  name="Primary"
  args={{
    primary: true,
    label: 'Button',
    onClick: action("clicked")
  }}>
  {Template.bind({})}
</Story>

argsを設定することで、以下のようにControlsタブが追加され、ボタンのがどのような状態を持つのかを画面上で確認できるようになります。

また、クリック時にActionsタブで詳細を確認することができます。

まとめ

Storybook、非常に良さそうですね。
特に、いちいちアプリを操作せずにコンポーネント単位で確認できるので、エンジニアもデザイナーも幸せに慣れそうな気がします。
他にも多くの機能があるので今後も触っていきたいです。
それでは!

10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?