Storybookを使用すると、UIコンポーネントを個別に開発できます。これにより、コンポーネントの再利用、テスト容易性、および開発速度を向上させることができます。

これらの開発者エクスペリエンス(DX)の利点に加えて、 Storybook は、従来のコンポーネント開発とうまく連携し、コンポーネントライブラリを表示、テスト、および文書化するためにコア機能を変更する必要はありません。

また、「アドオン」と「デコレータ」を介してさらに拡張することもできます。 アドオンを使用すると、ストーリーブックを開発フローとメッシュ化する可能性が解き放たれます。 次に、デコレータは、コンポーネントをスムーズに実行するために必要なカスタマイズを追加します。

Reactのストーリーブック

Storybookを使用してReactを起動して実行するのは、比較的簡単です。 完全なセットアップ手順は、公式ドキュメントにあります。 このプロセスにより、.storybook/ディレクトリとconfig.jsファイルが提供されます。 これらの要素が整ったら、Reactコンポーネントのストーリーを作成するのは簡単です。

//in Button.story.js

import React from 'react'
import { storiesOf } from '@storybook/react'

import Button from './Button'

storiesOf('Button', module)
  .add('default', () => (
    <Button>Submit</Button>
  ))
//in .storybook/config.js

import { configure } from '@storybook/react'

configure(function() {
  require('../src/components/Button.story')
}, module)
$ npm run storybook

コマンドを実行してストーリーブックを起動すると、ストーリーパネルに新しいストーリーが表示されます。

プロバイダーコンポーネント

Storybookはコンポーネントを個別に提供するため、Root.jsまたはApp.jsで一般的に見られる、アプリが提供する可能性のある機能の一部を高レベルで再現する際に問題が発生する可能性があります。 Reduxでは、これはプロバイダーになりますが、これは、react-routerのルーター、およびreact-i18nextなどのライブラリのプロバイダーまたはmaterial-uiなどのライブラリのThemeProviderにも適用されます。

コンポーネントコードを変更せずにこれを接続するには、Storybookが提供するヘルパーを使用してデコレータを作成します。 デコレータを使用すると、コンポーネントを共通のコードでラップして、冗長性や不整合を回避できます。

デコレータに飛び込む前に、すべての高レベルのプロバイダーからプロバイダーコンポーネントを作成します。 これは、Reduxプロバイダーのエクスポートと同じくらい簡単ですが、ルーター、国際化のセットアップなどを含めることもできます。

//in Provider.js

import React from 'react'
import { Router } from 'react-router'
import { Provider } from 'react-redux'

const ProviderWrapper = ({ children, store }) => (
  <Provider store={store}>
    <Router>
      { children }
    </Router>
  </Provider>
)

export default ProviderWrapper

プロバイダーをルートから分離すると、任意のコンポーネントストーリーに簡単にインポートできます。

プロバイダーのインポートに加えて、Reduxストアのインスタンスをインポートして渡す必要があります。

//in Button.story.js

...

import { storiesOf } from '@storybook/react'

import Provider from '../Provider.js'
import configureStore from '../configuedStore.js'

const store = configureStore()

...

storiesOf('Button', module)

これにより、ストーリーにstore.dispatchに簡単にアクセスできるため、Reduxアクションを呼び出してコンポーネントの状態変化をシミュレートできます。

デコレータ

Storybook Decorators を使用すると、コンポーネントが正しく実行する必要がある可能性のある共通のコードでストーリーをラップできます。 一般的な例は、ページのコンポーネントを中央に配置するラッパーですが、 a HOC のように、デコレータを使用してプロバイダーコンポーネントをストーリーに挿入します。

簡単な実装は次のようになります。

//in Button.story.js

// ...

import Provider from '../Provider.js'
import configureStore from '../configuedStore.js'

const store = configureStore()

const withProvider = (story) => (
  <Provider store={store}>
    { story() }
  </Provider>
)

storiesOf('Button', module)
  .addDecorator(withProvider)
  .add('default', () => (
    <Button>Submit</Button>
  ))

このwithProviderデコレータは、他のストーリーで使用するためのユーティリティ関数に変えることができます。 便宜上、これらのデコレータは通常、新しい.storybook/decorators.jsファイルに保存されるため、コードベース全体にインポートできます。

グローバルデコレータ

追加のヘルプとして、Storybookは、世界中のすべてのストーリーにデコレータを簡単に適用する方法を提供します。 これは、.storybook/config.jsaddDecoratorを呼び出すことで実行できます。

//in .storybook/config.js

import { configure, addDecorator } from '@storybook/react'
import { withProvider } from './decorators'

// ...

addDecorator(withProvider)

DevTools

Redux DevTools

Redux DevTools は、Reduxで開発する際の「パワーアップ」であり、状態の変化やアクションリプレイなどのビジュアルを提供します。

ブラウザ拡張機能をインストールした後、ストアで必要なコード構成がいくつかあります。

//in configureStore.js

import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(reducer, composeWithDevTools(
  applyMiddleware(...middleware),
))

これまで、Redux DevToolsは、すべてのコンポーネントをiframeに分離するため、Storybookの操作に問題がありました。 これにより、拡張機能がストーリー内のReduxアクションをリッスンできなくなりました。

これはもはやそれほど問題ではありませんが、役立つように作成されたアドオンがいくつかあります。

アドオン

Storybook Native Addons は、Storybookにさらに開発機能を追加します。 これらは、コミュニティによって作成された拡張機能でも、開発フローのカスタムでもかまいません。 構成の一部として、アドオンは新しい.storybook/addons.jsファイルに登録されます。

storybook-addon-redux-listener

この問題がより直接的に修正される前に、このRedux DevToolsリスナーアドオンは、ブラウザー拡張機能へのコンポーネントの接続を再確立しました。 また、ストーリーブック自体にシンプルなパネルを追加します。

構成は次のようになります。

//in .storybook/addons.js

import 'storybook-addon-redux-listener/register'
//in configureStore.js

import createStorybookListener from 'storybook-addon-redux-listener'

...

const middlewares = []

// OPTIONAL: attach when Storybook is active
if (process.env.NODE_ENV === 'storybook') {
  const reduxListener = createStorybookListener()
  middlewares.push(reduxListener)
}

const createStoreWithMiddleware = (reducers) => {
  return createStore(reducers, applyMiddleware(...middlewares))
}

const configureStore = () => createStoreWithMiddleware(reducers)

export default configureStore

アドオン-redux

このアドオンは、DevToolsの機能の一部を再現する複数のカスタムパネルを追加します。

//in .storybook/addons.js

import addons from '@storybook/addons'
import registerRedux from 'addon-redux/register'

registerRedux(addons)
//in configureStore.js

import { createStore, compose } from 'redux'
import reducer from './your/reducer'
import withReduxEnhancer from 'addon-redux/enhancer'

const configureStore = () => createStore(reducer, withReduxEnhancer)

export default configureStore

関連項目