Storybook
Storybookは、スタイルガイド・UIのカタログ集のようなものを構築できます。 VueやReactなどのフレームワークに対応しており、コンポーネント単体でどのようなUIになっているかの確認ができます。
エンジニア間だけでなく、デザイナーやディレクターとのコミュニケーションのツールとしても活用できます。
また、コンポーネント単体のテストとしても使用できます。
公式サイト: Storybook
活用するメリット
- 現在用意されているコンポーネント(UI)が一覧で確認できる
- コンポーネント単位での開発がしやすい
- データを書き換えた時のUIテストができる(簡易的なユニットテスト)
- エンジニア・デザイナー・ディレクター間のコミュニケーションツール
- 仕様書的な役割も担える
環境設定
今回はVue.js(Nuxt.js)向けの環境を作っていきます。
Nuxt.jsの初期設定は完了していることを前提とします。
インストール
Storybookはv5.2を使います。
$ npm install @storybook/vue@5.2.1 --save-dev
Storybookはフレームワークごとにインストール対象が分かれています。今回はVue.jsなので @storybook/vue
をインストールします。
Vue関連の依存モジュールをインストールします。
今回はすでにNuxt.jsでインストールしているものもあるので、babel-preset-vue
のみインストールします。
$ npm install babel-preset-vue --save-dev
必要に応じてVue関連の依存モジュールをインストールしてください。 https://storybook.js.org/docs/guides/guide-vue/#add-peer-dependencies
ファイル設定
まずは、npm scriptにStorybookコマンドを設定します。
// package.json
"scripts": {
...do something,
"storybook": "start-storybook"
},
Storybookのconfigファイルを設定します。
.storybook
ディレクトリを作り、その中にconfig.js
を用意します。
$ touch .storybook/config.js
// .storybook/config.js
import { configure } from '@storybook/vue';
configure(require.context('../components', true, /\.stories\.js$/), module);
Storybookのconfigure
関数にStorybookの対象となるファイルを指定します。上記の設定では、components
ディレクトリの.stories.js
が対象になります。
これで設定は終わりです。 Storybookの画面をブラウザで確認してみましょう。
$ npm run storybook
Storyを作る
それでは、Storyを追加していきましょう。(Storybookに追加するコンポーネントをStoryというらしい?)
components/Button.vue
というコンポーネントがあるのでこちらのStoryを作ってみます。
まずは、Storyのファイルを作ります。
$ touch ./components/Button.stories.js
次にVue.js本体とコンポーネントを読み込みます
// Button.stories.js
import Vue from 'vue';
import ButtonComponent from './Button';
グループわけする
Storyはグループ分けできます。
// Button.stories.js
import Vue from 'vue';
import ButtonComponent from './Button';
export default { title: 'Button' }; // Buttonという名のグループを指定する
export default { title: 'グループ名' };
でStoryを登録するグループを指定できます。
さらに細かくグループ分けすることも可能です。
// Button.stories.js
import Vue from 'vue';
import ButtonComponent from './Button';
export default { title: 'Atoms|Button' }; // Atoms/Buttonという名のグループを指定する
※他のコンポーネントを同じグループに登録するには、同一ファイルに記述する必要性があります。
コンポーネントをStorybookに表示する
では、Storybookでコンポーネントを表示しましょう。
// Button.stories.js
import Vue from 'vue';
import ButtonComponent from './Button';
export default { title: 'Atoms|Button' }; // Atoms/Buttonという名のグループを指定する
export const BasicButton = () => ({
components: { ButtonComponent },
template: '<ButtonComponent text="This is Basic Button" />',
});
関数を定義し、その戻り値としてVueコンポーネントを定義します。その関数名はStoryの名前(上記の例ではBasicButton
)になります。この関数をexport
することでStoryに登録されます。
状態変化を確認する
今回登録したButtonコンポーネントは、クリックする度に色が変わります。このように状態に応じたコンポーネントの変化をStorybookで確認できます。
Propsの変化に応じたUIの変化を確認する
前途の例では、コンポーネント自身のStateを変化された例でした。
次は、親から渡ってきたProps
に応じたUIの変化を確認します。
下記のようなボタンを用意しました。
ボタンをクリックすると何らかの処理が走ってローディング状態になります。ローディング状態は親で管理しているので、親から渡ってくるProps
に応じてUIが変化します。
こちらのStoryを作っていきます。
まずは、LoadingButton
のコンポーネントを追加します。
// Button.stories.js
import Vue from 'vue';
import ButtonComponent from './Button';
// 新しく追加
import LoadingButtonComponent from './LoadingButton';
LoadingButton
用のStoryを追加します。
// Button.stories.js
export const LoadingButton = () => ({
components: { LoadingButtonComponent },
template: `<LoadingButtonComponent
text="This is Loading Button"
:isLoading="isLoading"
:handle-click-button="handleClickLoadingButton"
/>`,
data() {
return {
isLoading: false,
}
},
methods: {
handleClickLoadingButton() {
this.isLoading = true;
setTimeout(() => {
this.isLoading = false;
}, 2000);
}
}
});
最初に作ったBasicButton
とは違って、親のStateをPropsとして渡しています。そして、ボタンがクリックされたことを通知を受けてisLoading
Stateを更新ししています。
これにより、Storybook上で親からのPropsに応じたUIの確認ができます。
Vuexとの連動
コンポーネントとVuexの連動をStorybookを使って確認したいと思います。
下記のコンポーネントは、Vuexを通じでAPIを叩き(json-server使用)、テキストを取得しています。
こちらをStorybookで再現します。
こちらをStorybookで再現します。
まずは、Vuex
などのモジュール読み込みと、初期化をします。
// Button.stories.js
import Vue from 'vue';
import Vuex, { mapState } from 'vuex';
import axios from 'axios';
// コンポーネントを追加
import LoadingButtonWithMessageComponent from './LoadingButtonWithMessage';
// Vuexの初期化
Vue.use(Vuex);
あとは、Storyのコンポーネントにstore
の設定を書くだけです。
// Button.stories.js
export const LoadingButtonWithMessage = () => ({
components: { LoadingButtonWithMessageComponent },
template: `
<LoadingButtonWithMessageComponent
text="Loading Button with Message"
:isLoading="isLoadingMessage"
:message="message"
:handle-click-button="handleClickFetchMessageButton"
/>`,
computed: {
...mapState({
isLoadingMessage: state => state.isLoading,
message: state => state.message,
})
},
methods: {
handleClickFetchMessageButton() {
this.$store.dispatch("setMessage", { message: "message" });
}
},
// storeの設定を直接かく
store: new Vuex.Store({
state: {
isLoading: false,
message: '',
},
mutations: {
setLoading(state, isLoading) {
state.isLoading = isLoading;
},
setMessage (state, { message, isLoading}) {
state.isLoading = isLoading
state.message = message
}
},
actions: {
async setMessage ({ commit }) {
commit('setLoading', true);
const { data } = await axios.get('http://localhost:3004/post');
commit('setMessage', { message: data.message, isLoading: false });
}
}
}),
});
storeの設定は長くなりやすいので、別ファイルで書いてimportしても良いかもしれません。
Addon
Storybookは基本的機能とは別に追加で機能を使うことができます。
その追加機能のことをAddon
と言います。
公式サイトで紹介されているAddonは下記になります。
Addonは個別にnpmからインストールします。
ここではいくつかのAddonを紹介します。
Knobs
propsの内容をブラウザ上で操作できます。
インストール・設定
$ npm install @storybook/addon-knobs --save-dev
Addonは基本的に登録設定を行います。
.storybook/addons.js
というファイルを作り、そちらに設定してしていきます。
$ touch .storybook/addons.js
// .storybook/addons.js
import '@storybook/addon-knobs/register';
登録されると、StorybookにAddonの表示がされます。
Storyに追加
前途したLoadingButton
の内容を変更してknobsを使ってみます。
まずは、モジュールを読み込みます。
// Button.stories.js
import {
withKnobs,
boolean,
text,
} from '@storybook/addon-knobs';
withKnobs
はStoryでknobsを使うためのモジュールです。
boolean
やtext
はPropsの型をしているます。
次にデコレータを使ってknobsを使用できるようにします。
// Button.stories.js
export default { title: 'Atoms|Button', decorators: [withKnobs] };
その後、コンポーネント側にPropsを設定します。
// Button.stories.js
export const LoadingButton = () => ({
components: { LoadingButtonComponent },
template: `<LoadingButtonComponent
:text="text"
:isLoading="isLoading"
:handle-click-button="handleClickLoadingButton"
/>`,
props: {
isLoading: {
type: Boolean,
default: boolean('IsLoading', false),
},
text: {
type: String,
default: text('Text', 'This is Loading Button'),
}
},
methods: {
handleClickLoadingButton(e) {
this.isLoading = true;
setTimeout(() => {
this.isLoading = false;
}, 2000);
},
}
});
Propsのdefalt
プロパティで先ほど読み込んだboolean
やtext
モジュールを使用しています。ここで指定したPropsをコンポーネントに渡すとStorybook上で操作できるようになります。
boolean
やtext
以外にも型設定を行うことができるので、公式サイトをご確認ください。
https://github.com/storybookjs/storybook/tree/master/addons/knobs#available-knobs
backgrounds
Storybook全体の背景色をブラウザ上で変更できます。
インストール・設定
$ npm install @storybook/addon-backgrounds --save-dev
// .storybook/addons.js
import '@storybook/addon-backgrounds/register';
Storyに追加
Story側に追加していきます。
// Button.stories.js
export default {
title: 'Atoms|Button',
decorators: [withKnobs],
// 追加
parameters: {
backgrounds: [
{ name: 'light', value: '#eeeeee' },
{ name: 'dark', value: '#222222', default: true },
],
},
};
ベースの設定にparameters
プロパティを追加して、その中にbackgrounds
パラメータを指定します。画面上で設定したい色を指定することができます。
Propsに応じて色が変わるコンポーネントなどの背景色との色合いを確認する時などに便利です。
viewport
Storybookないで複数のViewportの切り替えを行えます。
インストール・設定
$ npm install @storybook/addon-viewport --save-dev
// .storybook/addons.js
import '@storybook/addon-viewport/register';
addon-viewport
は登録のみで使用可能です。
HTMLファイルにビルド
これまで、Storybookの簡易サーバーを使ってStoryを作ってきましたが、出来上がったStorybookをHTMLとしてビルドすることもできます。
あらかじめ用意されたコマンド(build-storybook
)を叩くだけです。
$ npx build-storybook
上記を叩くと、index.html
が生成されます。このファイルを使ってデザイナーやディレクターに共有することができます。
build-storybook
コマンドにはいくつかオプション(出力先指定、ファイル監視など)があるのでこちらもチェックしてみてください。
https://storybook.js.org/docs/configurations/cli-options/#for-build-storybook
演習 - Storyを作ってみよう -
これまでの例を参考に実際にStoryを作ってみましょう。 こちらで用意したコンポーネント(※別途勉強会当日までに用意します)もしくは各自が考えるコンポーネントをStoryに追加します。
今回作るStoryとしては、下記が満たしているものを目指します。
- Storybook上でPropsの変更に応じたUIの変化を確認できる
- 何かしらのAddonを使用する
- Vuex + axios + json-serverを使ったStoryを作る ※これは可能であれば