フロントエンドのプロジェクトを始めるとき、きれいなデザインを考えることから始めます。細心の注意を払って、UI部品とステートやエフェクトを考えて描いていくでしょう。しかし、開発の最中でも次々に変更が発生します。新たな要求、予測していなかった使い方などが、いろいろなところで発生します。当初はかっこいいと思っていたコンポーネントだけではすべてをカバーできず、結局、新しいデザインを考えて拡張することになるのです。
それでも、その時点でデザイナーがチームにいればまだ良いほうです。多くの場合、デザイナーは別のプロジェクトに移っていて、あとから発生したデザイン変更には開発者が対処することになります。その結果、デザインの一貫性が崩れ、ライブラリーにどのような部品があるのか、状態や外観がどうなっているかを全部把握するのは難しくなります。
デザインの混乱を避けるために各コンポーネントごとに独立したスタイルガイドを作るのも良いでしょう。そのためのツールも多くあります。本記事では、Reactアプリのために開発された、手持ちのコンポーネントと動作を快適に閲覧し把握できるツールReact Storybook(以下、Storybook)を取り上げます。StorybookのサンプルとしてReact Native componentsギャラリーがあります。
なぜStorybookを使うのか?
ではStorybookがどのように役立つのでしょうか。その疑問に答える前に、UI開発に関わる人びとの要求を考えます。ワークフローによって要求は変わってきますが、よくあるパターンは以下のようなものです。
デザイナー、UXの専門家
デザイナー、UXの専門家はUIの外観や操作性に責任があります。モックアップでの検討が終わると、デザイナーはお役御免になりチームを離れることがほとんどです。あとから新たな要求が発生した際には、すばやくその時点でのUIステートを把握しなければなりません。
開発者
開発者たちがコンポーネントを実際に製作するので、スタイルガイドによる恩恵はおそらくもっとも大きいでしょう。開発者にとって主なStorybookの使い道は、ライブラリーからふさわしいコンポーネントを選び出したり、開発中にコンポーネントをテストしたりすることです。
テスター
テスターは、期待通りにコンポーネントが実装されているかを確認する几帳面な人たちです。主な仕事は、コンポーネントがあらゆる面で正しく動作するかを確認することです。こうした作業があるからといって統合テストが不要になるわけではありませんが、テスターの作業はプロジェクトの開発とは切り離して実施するほうが便利です。
プロダクトオーナー
プロダクトオーナーは完成品の所有者ですから、デザインや実装の受け入れの判断をします。彼らが求めるのは、サイトのどこをとっても期待通りの見た目になっていること、一貫してブランドイメージが表現されていることです。
気が付いたかもしれませんが、関係者すべてにとって好ましいのは、1カ所からすべてのコンポーネントが一覧できることです。プロジェクトの中からすべてのコンポーネントを確認するのは骨が折れます。考えてみてください。サイトで使う組み合わせ可能なボタン(有効・無効・プライマリ・セカンダリなどまで含む)を全部確認するのにどれだけ時間がかかると思いますか。だからこそ、どこかに一覧できるギャラリー、つまりStorybookがあれば便利なのです。
納得してもらえたなら、いよいよプロジェクトにStorybookをセットアップする方法を説明していきます。
Stroybookのセットアップ
Storybookのセットアップに最初に必要なものは、Reactのプロジェクトです。手元にちょうどいいものがなければ、create-react-appで簡単に作成できます。
Storybookを作成するには、全体にgetstorybookをインストールします。
npm i -g getstorybook
プロジェクトを開いて以下を実行します。
getstorybook
このコマンドは3つのことを実行します。
- プロジェクトに@kadira/storybookをインストールする
- package.jsonファイルにstorybookとbuild-storybookのスクリプトを加える
- 基本設定が入っている.storybookフォルダーと、コンポーネントとストーリーのサンプルが入ったstoriesフォルダーを生成する
Storybookを開始するにはnpm run storybookコマンドを実行し、表示されるアドレス(http://localhost:9009/)を開きます。次のように表示されます。
コンテンツを新規追加する
Storybookが準備できたので、さっそくコンテンツを追加します。ストーリーを作るごとに新規ページが追加されます。ストーリーというのは、各コンポーネントを描画するコードのかたまりです。getstorybookで生成されるサンプルストーリーは次のようになっています。
//src/stories/index.js
import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook';
import Button from './Button';
import Welcome from './Welcome';
storiesOf('Welcome', module)
.add('to Storybook', () => (
<Welcome showApp={linkTo('Button')}/>
));
storiesOf('Button', module)
.add('with text', () => (
<Button onClick={action('clicked')}>Hello Button</Button>
))
.add('with some emoji', () => (
<Button onClick={action('clicked')}> </Button>
));
storiesOf関数でナビゲーションメニューに新しいセクションを作り、addメソッドでさらにサブセクションを作ります。構成は好きなように作成できますが、2階層以上の階層は作れません。Storybookの分かりやすい構成方法は、たとえば、「Form input」「Navigation」「Widget」のような名前で関連する要素をまとめた階層を作り、その下に各コンポーネント用のサブセクションを作ることです。
ストーリーファイルの置き場所は自由です。別の専用フォルダーにしても良いし、コンポーネントのある場所でもかまいません。個人的には後者のほうがアクセスと更新がしやすいと考えています。
ストーリーは、以下のコードが書かれたstorybook/config.jsファイルに読み込まれます。
import { configure } from '@kadira/storybook';
function loadStories() {
require('../src/stories');
}
configure(loadStories, module);
初期状態のコードではsrc/stories/index.jsファイルを読み込んでいるので、ここにストーリーをインポートします。このままだと新規ストーリーを生成するたびにインポートが必要なので少し不便です。コードを変更して、Webpackのrequire.contextメソッドで自動的にすべてのストーリーが読み込まれるようにします。ストーリーファイルをほかのコードと区別するために拡張機能.stories.jsを追加します。変更したコードは次のようになります。
import { configure, addDecorator } from '@kadira/storybook';
import React from 'react';
configure(
() => {
const req = require.context('../src', true, /.stories.js$/);
req.keys().forEach((filename) => req(filename));
},
module
);
configure(loadStories, module);
別のフォルダーにコードを置いている場合は、正しい場所が指定されていることを確認してください。コードを反映するためにはStorybookを再度実行します。今度はindex.jsファイルを読み込まないので空ですが、このあと修正します。
新規ストーリーを作る
少しだけStorybookを自分仕様にしたところで、さっそく最初のストーリーを作ります。しかし、最初にストーリーを作るためのコンポーネントが必要です。色付きのブロックに名前表示するだけの簡単なコンポーネントNameを作ります。以下のようなJavaScriptとCSSで作成します。
import React from 'react';
import './Name.css';
const Name = (props) => (
<div className={'name ' + (props.type ? props.type : '')}>{props.name}</div>
)
Name.propTypes = {
type: React.PropTypes.oneOf(['highlight', 'disabled']),
}
export default Name;
.name {
display: inline-block;
font-size: 1.4em;
background: #4169e1;
color: #fff;
border-radius: 4px;
padding: 4px 10px;
}
.highlight {
background: #dc143c;
}
.disabled {
background: #999;
}
気が付いたと思いますが、この単純なコンポーネントには3つのステートがあります。default(初期)、highlighted(強調)、disabled(無効)です。ステートが視覚的に分かるようになれば便利です。そのストーリーを作ります。コンポーネントのある場所にName.stories.jsファイルを作成して以下のコードを書いてください。
import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook';
import Name from './Name';
storiesOf('Components', module)
.add('Name', () => (
<div>
<h2>Normal</h2>
<Name name="Louie Anderson" />
<h2>Highlighted</h2>
<Name name="Louie Anderson" type="highlight" />
<h2>Disabled</h2>
<Name name="Louie Anderson" type="disabled" />
</div>
))
Storybookを開き、作ったコンポーネントを確認すると、結果は次のようになっています。
ぜひサンプルのソースコードを好きなように修正して、コンポーネントがどのように表示されるかを確認してください。またReactのホットリロード機能のおかげで、ストーリーないしコンポーネントに加えた変更は、ブラウザーを手動で更新しなくても即座にStorybookに反映されます。ただし、ファイルを追加あるいは削除した場合は更新が必要です。いつでもStorybookが変更を自動で反映してくれるわけではありません。
ビューのカスタマイズ
ストーリーの表示を変更したいなら、コンテナクラスでラップします。これにはaddDecorator関数を使います。たとえば、すべてのページに「Examples」ヘッダーを加えるなら.storybook/config.jsファイルに以下のコードを加えます。
import { configure, addDecorator } from '@kadira/storybook';
import React from 'react';
addDecorator((story) => (
<div>
<h1>Examples</h1>
{story()}
</div>
));
同じようにstoriesOfのあとにaddDecoratorを実行すれば、個別のセクションにも変更を加えられます。
storiesOf('Components', module)
.addDecorator(...)
Storybookを公開する
Storybookの作業が完了して公開できる状態になったら、以下のコマンドを実行して静的なサイトをビルドします。
npm run build-storybook
初期状態ではStorybookはstorybook-staticフォルダーにビルドされます。出力先フォルダーは-oパラメーターで変更できます。あとは好きなホストにアップロードするだけです。
もしGitHubで作業しているなら、単にdocsフォルダーにビルドしてリポジトリに公開するだけで大丈夫です。GitHubではこの状態からGitHub Pagesを利用したWebサイトとして公開できます。作成したStorybookをリポジトリ内におきたくなければstorybook-deployerも使用できます。
ビルド設定
Storybookには、ストーリー内で使えるさまざまな機能が用意されています。コードはcreate-react-appと同じES2015+形式で書けますが、もし異なるBabelの設定を使用していても自動で.babelrcファイルを参照してくれます。またJSONファイルと画像もインポートできます。
これでも不十分なら.storybookフォルダー内にwebpack.config.jsファイルを作成すれば、Webpackの追加設定ができます。ファイルからエクスポートされた設定が初期設定に追加されます。例として、自分のストーリーにSCSSへの対応を追加する場合、以下のコードを加えてください。
module.exports = {
module: {
loaders: [
{
test: /.scss$/,
loaders: ["style", "css", "sass"]
}
]
}
}
上のようにする場合はsass-loaderとnode-sassを忘れずにインストールしてください。
この方法で、どのようなWebpack設定でも追加できますが、エントリー(entry)、出力(output)、Babelローダーについてはメソッドの上書きはできません。
もし開発用と本番用と、環境ごとに別々の設定にしたいなら、関数をエクスポートする方法があります。この関数は基本設定の引数storybookBaseConfigと変数configTypeを持つので「DEVELOPMENT」または「PRODUCTION」をセットします。
module.exports = function(storybookBaseConfig, configType) {
// add your configuration here
// Return the altered config
return storybookBaseConfig;
};
アドオンで機能を拡張する
Storybookはそのままでも非常に便利ですが、さらに改善するためのたくさんのアドオンがあります。記事ではいくつかを紹介しますが、ぜひ公式リストをチェックしてください。
ActionsとLinks
Storybookは最初から2つの設定済みアドオン、ActionsとLinksがあります。使用に際しては特に追加設定は必要ありません。
■Actions
Actions は、コンポーネントのイベントを「Action Logger」パネルでロギング(記録)するツールです。Storybookで生成されたButtonストーリーを確認してください。ボタンのonClickイベントが、UIにイベントを表示してくれるactionヘルパーにバインドされています。
注:実行するにはButtonストーリーを含むファイル名の変更、.storybook/config.jsに加えた変更によるファイルの場所の変更、の両方またはどちらかが必要かもしれません。
storiesOf('Button', module)
.add('with text', () => (
<Button onClick={action('clicked', 'test')}>Hello Button</Button>
))
ボタンをクリックして、「Action logger」の出力を確認してください。
■Links
Linksアドオンは、コンポーネント間の遷移を実現するツールです。どのonClickイベントにもバインドできるlinkToヘルパーが用意されています。
import { storiesOf, linkTo } from '@kadira/storybook';
storiesOf('Button', module)
.add('with link', () => (
<Button onClick={linkTo('Components', 'Name')}>Go to Name</Button>
));
ボタンをクリックすれば「Component」セクション、「Name」サブセクションに遷移します。
Knobs
Knobsアドオンは、実行の最中にUIからの操作でReactのプロパティを改変して対象のコンポーネントを変更できます。
アドオンをインストールするコマンドは、次のようになります。
npm i --save-dev @kadira/storybook-addon-knobs
アドオンを使用する前に、Storybookに認識させなければなりません。.storybookフォルダーにaddons.jsファイルを作成し以下のコードを加えます。
import'@kadira/storybook/addons'import'@kadira/storybook-addon-knobs/register'
変更のあとで、ストーリーをwithKnobs修飾子で囲みます。.storybook/config.jsファイル内全体で実行できます。
import '@kadira/storybook/addons';
import '@kadira/storybook-addon-knobs/register';
試しにNameコンポーネントのストーリーを変更します。3種類のステートが一度に表示される代わりに、UIからステートを選択できるようになりました。名前の編集もできます。Name.stories.jsファイルの中を次のように変更します。
import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook';
import { text, select } from '@kadira/storybook-addon-knobs';
import Name from './Name';
const types = {
'': '',
highlight: 'highlight',
disabled: 'disabled'
}
storiesOf('Components', module)
.add('Name', () => (
<div>
<h2>Normal</h2>
<Name name={text('Name', 'Louie Anderson')} type={select('Type', types)} />
</div>
))
このアドオンではユーザーの入力を受けるためのさまざまなヘルパーが用意されています。数値、範囲、配列などです。ここではNAME欄の変更のためにテキスト、TYPE欄の変更のためにselectを使います。「Name」ページを開くと「ACTION LOGGER」タブの隣に「KNOBS」タブが現れます。試しに値を変更して、コンポーネントが再描画されるのを確認してください。
Info
Infoアドオンはストーリーに、ソースコード、説明、Reactプロパティの型などの情報を追加するツールです。開発者にとってはこれらの情報はとても便利です。
このアドオンをインストールするコマンドは次のようになります。
npm i --save-dev @kadira/react-storybook-addon-info
次に.storybook/config.jsファイルを変更してアドオンをStorybookに登録します。
import { setAddon } from '@kadira/storybook';
import infoAddon from '@kadira/react-storybook-addon-info';
setAddon(infoAddon);
storiesOfオブジェクトにaddWithInfoメソッドが追加され、ストーリーを登録できるようになります。このメソッドは少し異なったAPIを持ち、ストーリーのタイトル、説明、レンダリング関数、追加設定を、パラメーターとして受け付けます。使用するにはNameストーリーを次のように変更します。
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import Name from './Name';
storiesOf('Components', module)
.addWithInfo(
'Name with info',
`
A component to display a colored name tag.
`,
() => (
<Name name="Louie Anderson" />
),
{ inline: true },
)
このインラインパラメーターによって、コンポーネントの情報が、隅のリンクからではなく初期状態で画面に表示されるようになります。結果は次のようになります。
テストの自動化
この記事でカバーしきれないStorybookの重要な一面は、テストの自動化のためのプラットホームとして使えることです。単体テストから機能やビジュアル自動化テスト(『やばっ!CSSの先祖返りで消耗したデザイナーにはビジュアル自動化テストがおすすめ』参照)まで、どのようなテストも実行できます。当然ですが、Storybookをテストプラットホームとして機能を強化するためのアドオンがあります。それだけで1つの記事になるほど重要なのでここでは詳しく説明しませんが、少しだけ触れておきましょう。
Specifications
Specificationsアドオンにより、ストーリーファイルに単体テストを直接書けるようになります。Storybookを開くたびにテストが実行され、UIに結果が表示されます。少し改造すれば、Jestを使ってCI環境からでもテストを実行できます。
『生まれ変わったFacebook製テストフレームワーク「Jest」とは何か?』を参照してください。
Storyshots
Storyshotsは、ストーリーでJestスナップショットテストを実行します。スナップショットテストにより、各コンポーネントを含むDOMが思っていた通りになっているかチェックできます。コンポーネントが正しく表示されるかを確かめるのに、少なくともDOMの観点からはとても便利です。
サービスとしてのStorybook
Kadiraはさらに「サービスとしてのStorybook」としてStorybook Hubも提供しています。ここに自分のStroybookを設置すれば、さらに高度な共同作業ができます。基本的な機能のほかにGitHub統合機能を備えており、プロジェクトに対する各プル・リクエストに対して新規Storybookを作成できます。またStorybookに直接コメントを残して、同僚たちと変更点に関してやりとりもできます。
最後に
もし自分のプロジェクトでUIコンポーネントの管理が苦痛だと感じるなら、一歩引いてなにがいけないのか考えてください。もしかしたら欠けているのは、関係する人が共同作業できるプラットホームかもしれません。それなら、Reactプロジェクトの場合はStorybookが最適な答えです。
※本記事はTim Severien、Giulio Mainardiが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
(原文:React Storybook: Develop Beautiful User Interfaces with Ease)
[翻訳:西尾健史/編集:Livit]