ReactのためのStorybook:UIコンポーネントを分離して開発する完全ガイド A to Z

Development tutorial - IT technology blog
Development tutorial - IT technology blog

すぐ試す:Storybookを5分で起動

既存のReactプロジェクトがあれば、Storybookの統合はわずか数コマンドで完了します:

cd my-react-app
npx storybook@latest init

このコマンドはReactを自動検出し、依存関係をインストールして、いくつかのサンプルStoryを生成します。完了したら実行:

npm run storybook

ブラウザでhttp://localhost:6006を開くと、サンプルコンポーネントがレンダリングされたStorybookのUIが表示されます。メインアプリの起動もログインもデータベースのセットアップも不要です。

Buttonコンポーネントの最初のStoryを作成します。Button.jsxと同じディレクトリにButton.stories.jsxを作成:

import Button from './Button';

export default {
  title: 'Components/Button',
  component: Button,
};

export const Primary = {
  args: {
    label: 'Click me',
    variant: 'primary',
  },
};

export const Disabled = {
  args: {
    label: 'Disabled',
    disabled: true,
  },
};

ファイルを保存してブラウザに戻ると、ButtonコンポーネントがサイドバーにPrimaryとDisabledの2つのStateとして表示されます。各Stateをクリックし、コードを変更せずにUIから直接propsを編集できます。

Storybookはどんな問題を解決するのか?

5万行のコードをリファクタリングした経験から学んだ最大の教訓は、着手前に十分なテストカバレッジを用意すること――特にUIコンポーネントに関しては。リファクタリング中には即座に確認が必要です:このButtonはレイアウトが崩れていないか?データが空のときCardコンポーネントはどう表示されるのか? アプリ全体を起動して5画面をナビゲートしてやっとコンポーネントを確認できるとしたら――それは純粋な時間の無駄です。

Storybookはまさにその問題を解決します:アプリのコンテキストに依存せずにコンポーネントを独立してレンダリングできます。各「Story」はコンポーネントの特定の状態を表します。10個のStateがあれば10個のStoryを作成し、Storybookを見るだけですべて確認できます。

Storybook導入前後のワークフロー

導入前: コンポーネントを修正 → アプリを起動 → 該当ページに移動 → 確認 → バグ発見 → 修正 → 繰り返し。1サイクルに2〜3分かかります。

導入後: コンポーネントを修正 → Storybookが自動リロード → 即座に確認。1サイクルわずか5秒です。

より大きなチームでは、Storybookはリビングドキュメントにもなります――デザイナーはStorybookを見るだけでコンポーネントの現在の状態を正確に把握でき、開発者に聞く必要がありません。QAは複雑なデータセットアップなしに、各Stateを体系的にテストできます。

深く理解する:Args、Controls、Play Functions

ArgsとControls — UIから直接propsを変更

Storybookの最も優れた機能の一つがControlsパネルです。argTypesを定義すると、Storybookが各propを編集するUIを自動生成します:

export default {
  title: 'Components/Card',
  component: Card,
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['default', 'outlined', 'elevated'],
    },
    loading: {
      control: 'boolean',
    },
    title: {
      control: 'text',
    },
  },
};

この設定により、Controlsパネルにはvariant用のドロップダウン、loading用のチェックボックス、title用のテキスト入力が表示されます。デザイナーやプロダクトマネージャーがコードを知らなくても、さまざまな組み合わせを自分で試せます。

Play Functions — Storybook内でインタラクションをテスト

Storybook 6.4以降では、Story内に直接インタラクションテストを記述できます:

import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';

export const FormSubmit = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText('Email'), '[email protected]');
    await userEvent.click(canvas.getByRole('button', { name: 'Submit' }));

    await expect(
      canvas.getByText('送信が完了しました!')
    ).toBeInTheDocument();
  },
};

StorybookはStoryを開いたときにこのPlay Functionを自動的に実行します。フォームへの入力、ボタンのクリック、結果の表示――すべてがブラウザのデベロッパーツールを開いたり別のテストファイルを書いたりすることなく、Storybook内で直接確認できます。

発展編:AddonsとCI/CDの統合

すぐにインストールすべき必須Addons

Storybookは豊富なAddonsエコシステムを持っています。よく使うものをいくつか紹介します:

  • @storybook/addon-a11y — 各コンポーネントのアクセシビリティ(WCAG)を自動チェック
  • @storybook/addon-viewport — さまざまなブレークポイント(モバイル、タブレット、デスクトップ)でコンポーネントをプレビュー
  • storybook-dark-mode — ダーク/ライトモードを切り替えてテーマをテスト
  • @storybook/addon-docs — JSDocとPropTypesからドキュメントを自動生成

.storybook/main.jsにAddonを追加:

export default {
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-a11y',
    'storybook-dark-mode',
  ],
};

Chromatic — 自動ビジュアルリグレッションテスト

Chromatic(Storybookチームが開発)はGitHub Actionsと統合し、各StoryのスナップショットをキャプチャしてBaselineと比較します。PRが出るたびに、Chromaticはどのコンポーネントのビジュアルが変わったかを検出してレビュアーにハイライトします。

npm install chromatic --save-dev
# .github/workflows/chromatic.yml
name: Chromatic
on: push
jobs:
  chromatic:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - run: npm ci
      - uses: chromaui/action@v1
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

CI/CDパイプラインと組み合わせることで、完全なフィードバックループが完成します:コードプッシュ → Chromaticがスナップショット撮影 → レビュアーがビジュアル変更を承認 → マージ。「なぜこのボタンが壊れているのに誰も気づかなかったの?」という状況はもう起きません。

Storybookを使う際の実践的なTips

Storiesはコンポーネントと同じディレクトリに置く、別フォルダに分けない

Button.stories.jsxは別のstories/フォルダではなく、Button.jsxと同じディレクトリに置きましょう。コンポーネントを削除するとStoryファイルも一緒に削除され、存在しないコンポーネントを指す「孤立したStory」が生まれることはありません。

エッジケースのStoryを書く — ハッピーパスだけでなく

UIバグの大半はエッジケースで発生します:テキストが長すぎる、画像が読み込まれない、データが空、ネットワークエラー。そうしたケースのStoryを作成しましょう:

export const LongText = {
  args: {
    title: '異なるビューポートでコンポーネントがオーバーフローしたりテキストが切れたりしないかテストするための非常に長いタイトルです',
  },
};

export const EmptyState = {
  args: { items: [] },
};

export const LoadingState = {
  args: { loading: true },
};

Decoratorsを使って一度Contextをモックし永続的に活用

多くのコンポーネントはRedux store、React Router、テーマプロバイダーなどのContextを必要とします。各ファイルで繰り返す代わりに、.storybook/preview.jsにグローバルDecoratorを宣言しましょう:

import { ThemeProvider } from './theme';
import { Provider } from 'react-redux';
import { store } from '../src/store';

export const decorators = [
  (Story) => (
    <Provider store={store}>
      <ThemeProvider>
        <Story />
      </ThemeProvider>
    </Provider>
  ),
];

早期にセットアップを、「時間ができたら」と思わずに

Storybookはコードベースが小さいうちがセットアップしやすい時期です。コンポーネントが100個になってからStoryを追加していくのは骨の折れる作業になります。私はこれを辛い経験から学びました――5万行のリファクタリングが終わって初めて、各コンポーネントのドキュメントを書くことを考え始めました。既存のコンポーネントのStoryを書くだけでさらに2週間かかりました。

最初からコンベンションを決めましょう:新しいコンポーネントはPRをマージする前に最低2〜3つのStoryが必要。完璧である必要はありませんが、必ず用意すること。それが長期的にStorybookを真に価値あるものにする唯一の方法です。

Share: