---
title: "VueJS で Storybook を使う"
description: "モジュラーと再利用可能なコンポーネントを作るために Vue と Storybook でコンポーネント ライブラリを構築する方法を学びましょう。"
authors:
  - name: "Dan Arias"
    url: "https://auth0.com/blog/authors/dan-arias/"
date: "Oct 23, 2018"
category: "Developers,Tutorial,Vue"
tags: ["vue", "storybook", "frontend", "component", "ui", "ux", "engineering", "component-library", "reusability", "kanban"]
url: "https://auth0.com/blog/jp-using-storybook-with-vuejs/"
---

# VueJS で Storybook を使う



Storybook はインタラクティブに開発し、ユーザー インターフェイス コンポーネントをアプリケーションを実行しないでテストできます。Storybook は独自の Webpack 構成でコンポーネント ライブラリとしての役目を果たすので、プロジェクト依存関係や要件を気にせずに個別に開発できます。

本投稿では、チームメートの Steve Hobbs 氏が作成した人気の[カンバンボード プログレッシブ Web アプリケーション（PWA）（GitHub で入手可能）](https://github.com/elkdanger/kanban-board-pwa)を使って、Storybook を既存の Vue.js プロジェクトに統合する方法を学んでいきます。このプロセスは新しい Vue プロジェクトにも使えます。

## カンバンボードによるプロジェクトを実行する

以下のコマンドを実行して、カンバンボードによるプロジェクトを起動してローカルで実行します。

```
git clone git@github.com:elkdanger/kanban-board-pwa.git
cd kanban-board-pwa/
npm install
npm run dev
```

アプリケーションが起動しているか確認するには、ブラウザーで [`http://localhost:8080`](http://localhost:8080/) を開きます。

![Preview of the Kanban Board application](https://images.ctfassets.net/23aumh6u8s0i/49m1cMiwvXiVavyb0h2A1F/d5eae02642a7c877d00620b11c6af92b/kanban-board-part1_preview)

> Storybook を使うためにアプリを実行する必要はありません。ご希望であれば、それを停止してブラウザー タブを閉じます。

最後に、`kanban-board-pwa` プロジェクトをご希望の IDE またはコード エディタで開きます。

## Storybook を Vue で設定する

現行の作業ディレクトリとして `kanban-board-pwa` を次のコマンドで実行し、`npm` を使って Storybook をインストールします。

```bash
npm i --save-dev @storybook/vue
```

Storybook は `vue` と `babel-core` がインストールされている必要があります。`kanban-board-pwa` は Vue CLI を使って作成されたので、これら2つの依存関係はすでにインストールされています。

最後に、Storybook を簡単に始めて実行できる `npm` スクリプトを作ります。`package.json` ファイルの `scripts` セクションの下に、次を追加します。

```json
{
  // ...
  "scripts": {
    // ...
    "storybook": "start-storybook -p 9001 -c .storybook"
  }
  // ...
}
```

`-p` コマンド引数は Storybook がローカルで実行するポート、この場合は `9001` を指定します。`-c` コマンド引数は Storybook に構成設定の `.storybook` ディレクトリを探すように伝えます。これは次で実行します。

## Storybook を Vue で構成する

Storybook は多くの異なる方法で構成されます。ベストプラクティスとして、その構造は `.storybook` と呼ばれるディレクトリに保存します。ルート フォルダーの下にそのディレクトリを作ります。

```bash
.
├── .babelrc
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .git
├── .gitignore
├── .idea
├── .postcssrc.js
├── .storybook // Storybook config directory
├── Dockerfile
├── LICENSE
├── README.md
├── build
├── config
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── screenshots
├── src
└── static
```

`.storybook` 内にすべての構成設定を保留する `config.js` ファイルを作ります。ここで、Storybook 対応の Vue アプリケーションを作る重要な要素を定義します。

### Vue を定義する

`src/main.js` ファイルと同様に、`vue `をインポートする必要があります。`config.js` を次のように更新します。

```js
// .storybook/config.js

import Vue from 'vue';
```

### Vue コンポーネントを定義する

丁度 Vue プロジェクトで行ったように、グローバル カスタムコンポーネントの `Vue.component` でインポートして`グローバルに登録する必要があります。config.js` インポートを更新し、`TaskLaneItem` コンポーネントを登録します。

```js
// .storybook/config.js

import Vue from 'vue';

// カスタムコンポーネントをインポートします。
import TaskLaneItem from '../src/components/TaskLaneItem';

// カスタムコンポーネントを登録します。
Vue.component('item', TaskLaneItem);
```

これは、Storybook は Vue アプリケーションとは個別に実行するので、実行しなければなりません。ローカルに登録されたコンポーネントは自動的に持ち込まれるのでご注意ください。これらは Vue コンポーネント オブジェクトの `components` プロパティを使って登録されたコンポーネントです。例えば：

```js
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
});
```

`ComponentA` および `ComponentB` は #`app` の下にローカルに登録されます。

`TaskLaneItem` はグローバル カスタムコンポーネントなので Storybook 内に独立してインスタンスを作成するためにそれをインポートして Vue で登録しなければなりません。

### ストーリーを構成し読み込む

`@storybook/vue` から `configure` メソッドをインポートして Storybook を実行し、**ストーリー**（ストーリーについては以下で学びます）を読み込むためにそれを実装します。

```js
// .storybook/config.js

import { configure } from '@storybook/vue';

import Vue from 'vue';

// カスタムコンポーネントをインポートします。
import TaskLaneItem from '../src/components/TaskLaneItem';

// カスタムコンポーネントを登録します。
Vue.component('item', TaskLaneItem);

function loadStories() {
  // 必要なだけのストーリーを要求できます。
}

configure(loadStories, module);
```

Storybook はツールをテストするのと同じように機能します。`Config.js` ファイルが `configure` メソッドを実行し、それによって `loadStories` という関数と `module` を引数として取ります。`loadStories` はそのボディに定義された**ストーリー**があります。

**ストーリー**は指定された状態でコンポーネントを説明します。コンポーネントには `active`、`inactive`、`loading` などの各状態のストーリーを書き込みます。その後、Storybook ではインタラクティブなコンポーネントライブラリでその指定された状態のコンポーネントをプレビューできます。これは後ほど設定していきます。

より良いプロジェクト マネジメントのため、このストーリーはコンポーネントの隣りに保存するのが理想的です。`src` 内に `stories.js` ファイルを生成して使用するすべてのストーリーをホストします。それから `stories.js` ファイルを使って、次のように `config.js` ファイルにそのストーリーを素早くロードします。

```js
// .storybook/config.js

// ...

function loadStories() {
  // 必要なだけのストーリーを要求できます。
  require('../src/stories');
}

configure(loadStories, module);
```

`loadStories` を実行するとき、Storybook は `src/stories.js` にあるすべてのストーリーをインポートして実行します。このように Storybook の構成よりもその実装にとことん集中することで `config.js `ファイルのメンテナンスがずっと簡単になります。現時点ではすべてのアクションが `src/stories.js` ファイル内で発生します。

> すべてのカスタムコンポーネントと Vue プラグインは configure() を呼び出す前に登録します。

<include src="TweetQuote" quoteText="「Storybook はインタラクティブに開発し、ユーザー インターフェイス コンポーネントをアプリケーションを実行しないでテストできます。それと VueJS を統合する方法について学びましょう。」"/>

## Vue の Storybook ストーリーを書き込む

Storybook ストーリーを書いて、コンポーネントライブラリを現実のものにしましょう。`src/stories.js` に移動し、次のように始めます。

```js
// src/stories.js

import { storiesOf } from '@storybook/vue';

storiesOf('TaskLaneItem', module);
```

ここまでで `storiesOf` メソッドをインポートしました。このメソッドはコンポーネントのストーリーを作るのに役立ち、この場合、`TaskLaneItem` コンポーネントを使ってその役割を果たします。`TaskLaneItem` は `Vue.component` ですでにグローバルに登録されているので、それを `src/stories.js` にインポートする必要はありません。Using, Storybook の宣言型言語を使って `TaskLaneItem` のストーリーを次のように伝えます。

```js
storiesOf('TaskLaneItem', module);
```

このプロセスを実際の書籍で考えると、これは書籍の製本や表紙になります。では、これらストーリーをページに埋めます。これを宣言的に `add` メソッドを使って行います。

```js
// src/stories.js

import { storiesOf } from '@storybook/vue';

storiesOf('TaskLaneItem', module).add('Default TaskLaneItem', () => ({
  template: '<item :item="{id: 10, text: \'This is a test\'}"></item>'
}));
```

`add` はストーリーがある書籍に章を追加するのと同じような役目をします。各章にタイトルを付けます。この場合、`Default TaskLaneItem` というタイトルのストーリーを作ります。`add` はそのストーリータイトルと、段階的なコンポーネントをレンダリングする関数を引数として取ります。この場合、コンポーネントは `template` オプションを指定した Vue コンポーネント定義オブジェクトです。

**重要な注意事項：**コンポーネントを表す、テンプレートで使用されるタグ名は `Vue.component` で `.storybook/config.js` にコンポーネントを登録したときに使用した名前です。

```js
// カスタムコンポーネントを登録します。
Vue.component('item', TaskLaneItem);
```

この Vue コンポーネント定義オブジェクトは `id` と `text` を控えめな `props` にしてモジュラーを増やすためにリファクタリングできます。

```js
// src/stories.js

import { storiesOf } from '@storybook/vue';

storiesOf('TaskLaneItem', module).add('Default TaskLaneItem', () => ({
  data() {
    return {
      item: { id: 10, text: 'This is a test' }
    };
  },
  template: '<item :item="item"></item>'
}));
```

ストーリーを書く基盤ができました。では Storybook を実行してその機能を確認しましょう。

<include src="TweetQuote" quoteText="「Storybook はコンポーネント ライブラリにコンパイルされたストーリーを通して個別のコンポーネントのプレゼンテーションと状態を説明します。VueJS の Storybook ストーリーを書く方法を学びましょう。」"/>

## Storybook を実行する

コンピューターで次のコマンドを実行します。

```bash
npm run storybook
```

すべての実行が成功したら、コンソールに次のメッセージが表示されます。

```shell
info Storybook started on => http://localhost:9001/
```

ブラウザーで URL [`http://localhost:9001/`](http://localhost:9001/) を開きます。ロードしたら、ご自分の Storybook をお楽しみください。

![Vue Storybook up and running.](https://images.ctfassets.net/23aumh6u8s0i/7s3ft0N6UWqUTkNkXhbHlp/2673b88d3a79004cd1305f2f34a62b47/vue-storybook)

この状態では `TaskLaneItem` コンポーネントはあまり良くありません。それはライブ アプリケーションで見たときと比較してテキストが読みにくいからです。

![Preview of the Kanban Board application](https://images.ctfassets.net/23aumh6u8s0i/49m1cMiwvXiVavyb0h2A1F/d5eae02642a7c877d00620b11c6af92b/kanban-board-part1_preview)

`TaskLaneItem` コンポーネントの定義を保留する `src/components/TaskLaneItem.vue` ファイルを開きます。背景色が定義されている以外はスタイル指定がされていないことが分かります。このコンポーネントの完全なスタイル指定は Bootstrap から来ます。よって、次のステップは Storybook に Bootstrap を使用させることです。

## カスタムヘッドタグを Storybook に加える

`index.html` を開くと、`kanban-board-pwa` アプリは `<head>` 要素内で異なるタグを使っていることが分かります。Storybook で正確にコンポーネントをプレビューするための2つの関連タグは Bootstrap と FontAwesome をプロジェクトに紹介する `<link>` タグです。

Storybook はそのアプリとは個別に実行するので、`index.html` 内で定義されたこれらタグを見たり使用したりすることはできません。ソリューションとして、`.storybook` 構成ディレクトリの下に `preview-head.html `ファイルを作成して必要な `<link>` タグを次のように追加します。

```js
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootswatch/4.0.0-beta.2/superhero/bootstrap.min.css">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
```

それを停止して Storybook を再起動してから `npm run storybook` を再び実行します。これで Storybook が更新した構成を使用できるようになります。再びブラウザーで <code>[http://localhost:9001/](http://localhost:9001/)</code> を開くと、同じスタイル設定でずっと良い `TaskLaneItem` をフルバージョンのアプリでプレビューすることができます。

![Vue Storybook using Bootstrap](https://images.ctfassets.net/23aumh6u8s0i/2jFZFPuKXwPIR3XknvEOV2/391d78ce4fb952e90fb6c02ff54a0ff3/vue-storybook-using-bootstrap)

## コンポーネント変更を Storybook でプレビューする

これまで既存の定義と構成でコンポーネントを段階的に実行してきました。Storybook の最も強力な機能はアプリケーションを実行せずに、変更をライブで見ることができることです。`TaskLaneItem` オレンジ色内のテキストの色にしたいとするとき、そのパディングを増やし、オレンジ色の境界線を追加します。いかがですか？`TaskLaneItem.vue` にある `<style>` タグ内で変更します。

``` html  
// src/components/TaskLaneItem.vue
<template>
  // ... Template definition
</template>
<script>
  // ... Script definition
</script>
<style>
.card.task-lane-item {
  background: #627180;
  border: solid orange 5px;
}
.card-title {
  color: orange;
}
.card-block {
  padding: 20px;
}
</style>
```

そのファイルを保存すると、Storybook を更新したコンポーネントのプレビューをすぐに見ることができます！

![Modified component preview in Storybook](https://images.ctfassets.net/23aumh6u8s0i/5sIyAyQkUzj8lFF8RATFfe/c4579af0554e0c67a43cdc7d8fbefd3e/modified-component-preview-in-storybook)

個別のコンポーネントの外観や印象を実験することで時間の節約にもなります。UI パズル全体を組み立てる代わりに、ひとつのデータだけをプレビューできます。ただし Storybook はコンポジションの中でコンポーネントを実行できます。

次に進む前に、変更した `TaskLaneItem` のスタイルをリバースするだけでかなり良くなります。

## Storybook で Vuex を使用する

ここでは簡単に提示するコンポーネントをプレビューする方法を学びましょう。では、さらに複雑な構造で、データと一緒にハイドレートするために Vuex ストアを使って `TaskLane` コンポーネントのストーリーを作りましょう。まず `.storybook/config.js` を更新して`TaskLane`をインポートして登録します。

```js
// .storybook/config.js

// ...

// カスタムコンポーネントをインポートします。
import TaskLaneItem from '../src/components/TaskLaneItem';
import TaskLane from '../src/components/TaskLane';

// ...

// カスタムコンポーネントを登録します。
Vue.component('item', TaskLaneItem);
Vue.component('lane', TaskLane);

// ...
```

次に、`src/stories.js` に戻って `TaskLane` のストーリーを作ります。

```js
// src/stories.js

// ... TaskLaneItem stories

storiesOf('TaskLane', module).add('Default TaskLane', () => ({
  data() {
    return {
      doneItems: [
        { id: 10, text: 'This is a test' },
        { id: 12, text: 'This is another test' },
        { id: 14, text: 'This is yet another a test' },
        { id: 16, text: 'This is one more test' }
      ]
    };
  },
  template: `
      <div class="col-md">
        <lane id="done" title="Done" :items="doneItems"></lane>
       </div>
    `
}));
```

これは前に `TaskLaneItem` で実行した方法と同様で、主な違いは col-md クラスで `div` 内に `lane` を折り返し `doneItems` の配列を `items` プロパティを通して `TaskLane` にパスすることです。

> 前に、`Vue.component `でコンポーネントを登録するために使った名前なので、コンプレート内のコンポーネントタグ名として `lane` を使っていました。

`src/components/TaskLane.vue` ファイルで見たように `TaskLane` は `TaskLaneItem` をローカルで登録するので、Storybook は自動的に `TaskLaneItem` をこのストーリーの `TaskLane` のスコープに持ち込みます。`Default TaskLane` ストーリーのコンポーネント定義オブジェクト内に `components` プロパティを作る必要はありません。

そのファイルを保存します。その変更はそのページをリフレッシュするまで Storybook に正しく表示されないかもしれないので、リフレッシュしてください。`TaskLane` メニュー項目をクリックしてそれを拡大しそれから `Default TaskLane` をクリックします。`TaskLane` コンポーネントのプレビューが次のように表示されます。

![Complex component preview in Storybook](https://images.ctfassets.net/23aumh6u8s0i/2rGVAd0q1CHLT3d5celgk/e4dd98504152227df7a8f9cc50de9ead/complex-component-preview-in-storybook)

`TaskLane` はかんばんボードにタスクをリストするために `TaskLaneItem` を使用します。これら `TaskLaneItem` コンポーネントはこのアプリで構成されているように、`TaskLane` コンポーネントの間にドラッグ アンド ドロップします。Storybook `TaskLane` プレビューが完全にインタラクティブになったことが分かります。レーン内に表示されている項目はドラッグして動かすことができます。

ただし、レーン内にコンポーネントをドラッグすると、その位置は固定されません。開発者コンソールを開くと、次のようなエラーが見えます。

```bash
vue.esm.js:591 [Vue warn]: Property or method "$store" is not defined on the instance but referenced during render.
```

どうしたのでしょうか？`src/components/TaskLane.vue` ファイルにある `TaskLane` の定義に移動します。このコンポーネントはこのプロジェクトで作成された `vuex `ストアを使用して項目を更新することが分かります。

``` html  
// src/components/TaskLane.vue
// ... Template tag
<script>
// ... Script imports
export default {
 // ... Other properties
```

```javascript
  computed: {
    itemCount() {
      if (!this.items) return '';
      if (this.items.length === 1) return '1 task';
      return `${this.items.length} tasks`;
    },
    draggables: {
      get() {
        return this.items;
      },
      set(items) {
        this.$store.commit('updateItems', {
          items,
          id: this.id
        });
      }
    }
  }
};
</script>
// ... Style tag
```

`vuex` ストアのインスタンスは `$store` を通じて存在します。それはレーンをタスクレーン項目が属するものに更新する `updateItems` 変更をコミットするために使用されます。ただし、ストーリー内の `TaskLane` のインスタンスはこのストアの存在に非対応です。さらに重要なのは、項目の配列をレンダリングするために手動で `TaskLane` にパスしています。なぜこのような問題が発生するのでしょうか。

`kanban-board-pwa` アプリケーションのアーキテクチャはアプリケーションの状態に対する信頼できる唯一の情報源として Vuex ストアを使用します。データをレンダリングする必要があるコンポーネントはストアから入手しなければなりません。Vuex ストアは再有効化されているので、ストアのストラクチャが変更されると、影響を受けたデータにサブスクライブされたコンポーネントは更新されます。

問題は Storybook サンドボックス内にあり、ストアはデータで初期化されていません。また、`TaskLane` はその `items` データをその親コンポーネントである `KanbanBoard` から取得します。`KanbanBoard` コンポーネント定義は `src/components/KanbanBoard.vue` でご覧いただけます。この親コンポーネントは `TaskLane` コンポーネントにパスした算定されたプロパティを作るストアをクエリします。

``` html  
// src/components/KanbanBoard.vue
<template>
  <div class="board">
    <div class="row">
        <div class="col-md">
          <task-lane id="todo" title="Todo" :items="todoItems"></task-lane>
        </div>
        <div class="col-md">
          <task-lane id="inProgress" title="In progress" :items="inProgressItems"></task-lane>
        </div>
        <div class="col-md">
          <task-lane id="done" title="Done" :items="doneItems"></task-lane>
        </div>
    </div>
  </div>
</template>
<script>
import { mapState } from 'vuex';
import TaskLane from './TaskLane'
export default {
  name: 'KanbanBoard',
  components: {
    'task-lane': TaskLane
  },
  computed: mapState({
    todoItems: s => s.items.todo,
    inProgressItems: s => s.items.inProgress,
    doneItems: s => s.items.done
  })
};
</script>
```

個別に `Tasklane` インスタンスを作成することが Storybook を使用する目的です。ストアからの項目プロパティを `Tasklane` にパスできるために `KanbanBoard` 全体のインスタンスを作ることはソリューションでも、ストアがまだ空だからという理由でもありません。この問題を解決する最初のステップは Storybook プロジェクトが作成されたときに、項目をストアに追加することです。

`.storybook/config.js` に移動し、次のようにファイルを更新します。

```js
// ...その他のインポート
import store from '../src/store';

// ..カスタムコンポーネントをインポートします。

store.commit('addItem', { text: 'This is a test' });
store.commit('addItem', { text: 'This is another test' });
store.commit('addItem', { text: 'This is one more test' });
store.commit('addItem', { text: 'This is one more test' });

// ...カスタムコンポーネントを登録します。

// ...
```

ここで Storybook プロジェクトが構築されたら、ストアはこれら４つの項目で読み込まれます。ただし、Storybook プロジェクトのファイルに変更すると、その項目はブラウザーのローカル ストレージに保存されているので、重複の項目が表示されます。これは Storybook がホストされているブラウザー ウィンドウを再度読み込みして解決します。

各項目の `id` はそのストアによって自動的に作成されます。次に、ストア項目をプロパティとして `TaskLane `コンポーネントにパスするために `src/stories.js` 内の `Default TaskLane` ストーリーを更新します。

```js
// src/stories.js

import { mapState } from 'vuex';
import { storiesOf } from '@storybook/vue';
import store from '../src/store';

// ...TaskLaneItem ストーリー

storiesOf('TaskLane', module).add('Default TaskLane', () => ({
  computed: mapState({
    items: s => [...s.items.todo, ...s.items.inProgress, ...s.items.done]
  }),
  store,
  template: `
      <div class="col-md">
        <lane id="todo" title="Todo" :items="items"></lane>
       </div>
    `
}));
```

ストアから計算されたゲッター関数を生成するために `src/components/KanbanBoard.vue` 内にあるロジックと同様に、<code>[mapState](https://vuex.vuejs.org/guide/state.html#the-mapstate-helper)</code> を使います。

項目を追加するとき、`kanban-board-pwa` アプリの機能を理解していることが重要です。`KanbanBoard` テンプレートに戻ると、各レーンに `id` 値が与えられていることが分かります。

```html
// src/components/KanbanBoard.vue

<template>
  <div class="board">
    <div class="row">
        <div class="col-md">
          <task-lane id="todo" title="Todo" :items="todoItems"></task-lane>
        </div>
        <div class="col-md">
          <task-lane id="inProgress" title="In progress" :items="inProgressItems"></task-lane>
        </div>
        <div class="col-md">
          <task-lane id="done" title="Done" :items="doneItems"></task-lane>
        </div>
    </div>
  </div>
</template>
```

その `id `はストアの配列記号と（意図的に）一致するので 、データのオーバーヘッドがあまりなく、リストを劇的に更新する簡易対応になり、このデモのスコープにはぴったりのソリューションです。

作業を保存して Storybook をリフレッシュします。次をご覧ください。

![Vuex component presented in Storybook](https://images.ctfassets.net/23aumh6u8s0i/4gYBcCVDPH3wcR0QLGg4AX/b3e5eeee8cace6d19b3c2fd6b41e8b20/vuex-component-in-storybook)

項目をレーン内に並べ替え、これらが新しい位置を覚えているかご覧ください。

![Vuex component with rearranged items presented in Storybook](https://images.ctfassets.net/23aumh6u8s0i/6ZHtHgnWcm5BvusYqCqr1o/c47e85b18674b5d77c223decbf7626fc/vuex-component-with-rearranged-items-in-storybook)

`vuex` ディレクトリを使う必要はありませんが、ストア内に一からストアを作成したい場合、`vuex` をインポートしそれを次のようにインストールします。

```js
// .storybook/config.js

import Vuex from 'vuex'; // Vue plugins

// Vue プラグインをインストールします。
Vue.use(Vuex);
```

それから新しいストアのインスタンスを次のようにストーリー内に作ります。

```js
import Vuex from 'vuex';

storiesOf('Component', module)
  .add('Default Component', () => ({
    store: new Vuex.Store({...}),
    template: `...`
  }));
```

`vuex` のような必須 Vue プラグインは [Vue.use](https://vuejs.org/v2/api/#Vue-use) を使ってインストールする必要があります。

## 複合コンポーネントを Storybook に組み立てる

項目をレーン内にドラッグ アンド ドロップしてそれらがどのように並べ替えられるかを学ぶことはよかったですが、項目をレーンの間にドラッグ アンド ドロップできることはさらに素晴らしいことです。では、フルバージョンのアプリに存在する「Todo」、「In Progress」、「Done」の3つのレーンを表すストーリーを作りましょう。これは `KanbanBoard `コンポーネントのインスタンスを作らないで完了します。 `src/stories.js` に移動し、`Three TaskLanes` ストーリーを作ります。

```js
// src/stories.js

// ... インポートします

// ...TaskLaneItem ストーリー

storiesOf('TaskLane', module)
  .add('Default TaskLane', () => ({
    computed: mapState({
      items: s => [...s.items.todo, ...s.items.inProgress, ...s.items.done]
    }),
    store,
    template: `
      <div class="col-md">
        <lane id="todo" title="Todo" :items="items"></lane>
       </div>
    `
  }))
  .add('Three TaskLanes', () => ({
    computed: mapState({
      todoItems: s => s.items.todo,
      inProgressItems: s => s.items.inProgress,
      doneItems: s => s.items.done
    }),
    store,
    template: `
      <div class="row">
        <div class="col-md">
          <lane id="todo" title="Todo" :items="todoItems"></lane>
        </div>
        <div class="col-md">
          <lane id="inProgress" title="In progress" :items="inProgressItems"></lane>
        </div>
        <div class="col-md">
          <lane id="done" title="Done" :items="doneItems"></lane>
        </div>
    </div>
    `
  }));
```

ここでは、レーン項目の各カテゴリ用に算出されたゲッター関数を生成するために `mapState` を使う `KanbanBoard` に存在する同じ算出されたプロパティを使います。3つのレーンはストアの配列シンボルに一致する「`Todo`」、「`InProgress`」、「`Done`」で `id` 値として作成されます。 この作業を保存し、Storybook に戻ります。`TaskLane` メニューのタブをクリックし、それから `Three Tasklanes` をクリックします。そうすると、次のようなものが表示されます。

![Storybook showing three lane components.](https://images.ctfassets.net/23aumh6u8s0i/6vagdIcVAEtXGtvi8Jn82l/8f4492346ca7058b3070b180a7fb2af7/storybook-showing-three-lane-components)

> 重複項目があったら Storybook ウィンドウをリフレッシュします。

ここで、項目をレーン間にドラッグ アンド ドロップします。各項目はその新規レーンとレーンの新規位置を記憶します。良い機能ですね？

![Storybook showing three lane components with spread out items.](https://images.ctfassets.net/23aumh6u8s0i/3FCH0PeqPI0WuBGXUEgsZO/272a6e59daa6d73a4ab2c3f58d08e4e4/storybook-showing-three-lane-components-with-spread-out-items)

下部から上部にコンポーネントを作る練習をしましょう。小さなk提示可能なコンポーネントから始め、そのスタイルやコンテンツを調整し、それからプレゼンテーションのコンポーネント構成に依存する大きめのコンポーネントに移動します。Storybook では UI パズルの真にモジュラー非依存型データのような各コンポーネントを処理することに集中できます。

大規模なチームにとってのもうひとつの利点は、コンポーネント ライブラリを作ることでプロジェクト内だけでなく、効果的にブランディングを行うために一貫した外観と印象を必要とする組織のプロジェクト全体でコンポーネントを再利用できることです。

## まとめ

`vuex` のような Vue プラグインにアクセスしたり使用したりしながら Storybook のコンポーネント ライブラリを通してインタラクティブな方法で基本的なプレゼンテーションや複雑なコンポーネントをプレビューする方法を学びました。Storybook はボタンをクリックして機能を起動したり UI テストのためなどに、コンポーネントを通してトリガーされるアクションをプレビューするためにも使うことができます。これらは高度な使用事例です。イベント処理やテストを扱うために、Vue 用に Storybook を拡張することについてのブログ投稿についてご関心のある方は以下のコメント欄でお知らせください。

> Auth0 では、Storybook を広範囲にわたって使ってきました。Storybook でコンポーネント ライブラリを作ることについてご覧になりたい方、私たちが見つけた利点を知りたい方は_[「React および Storybook でコンポーネント ライブラリを設定する」](https://auth0.com/blog/setting-up-a-component-library-with-react-and-storybook/)_ についての投稿をご覧ください。

## Auth0：絶対に ID で妥協しない

アプリケーションの構築についてご関心がありますか？そのためにはユーザー認証を設定する必要があります。一から実装するのは複雑で時間がかかるかもしれませんが、Auth0 を使うと数分でできます。詳細については、[https://auth0.com](https://auth0.com/) をご覧いただくか、[@auth0 on Twitter](https://twitter.com/auth0) をフォローしてください。

<br />

<p style="text-align: center;">
  <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/U3NDciLTTgI" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
</p>
