React・実務の道具立て

部品をカタログにする(Storybook)

「お気に入り済み」「メモなし」「名前が長い」を画面から作るのは面倒です。Storybookで状態ごとに並べて、そのまま確認・レビュー・アクセシビリティ検査まで回せるようにします。

ことりずかんのカードは、状態によって見た目が変わります。

  • お気に入り済み(★)と、まだ(☆)
  • メモがある鳥と、ない鳥(「せつめいはじゅんび中」)
  • レアの印が付く鳥と、付かない鳥
  • 名前が長い鳥(レイアウトが崩れないか)

これを画面から確かめようとすると面倒です。メモが空の鳥を出すためにJSONを書きかえ、確認したら戻す。名前が長いときの確認は、たいてい忘れます。

Storybookは、部品を状態ごとに並べておくカタログです。

入れる

npx storybook@latest init

質問に答えると、プロジェクトを見て(Vite+React+TypeScriptだと判定して)設定まで書いてくれます。追加されるのはこのあたりです。

.storybook/
├── main.ts        どのファイルをストーリーとして読むか、アドオンの設定
└── preview.tsx    全ストーリー共通の設定(CSSの読みこみなど)
src/stories/       サンプル(消してよい)

package.jsonにも2つ増えます。

"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"

Tailwindを使っているなら、preview.tsxの先頭でCSSを読ませます。これを忘れるとStorybookの中だけスタイルが当たりません。

import '../src/index.css'

ストーリーを書く

BirdCardのストーリーです。部品と同じフォルダに置くのが今の主流です。

import type { Meta, StoryObj } from '@storybook/react-vite'
import { MemoryRouter } from 'react-router-dom'
import { fn } from 'storybook/test'
import BirdCard from './BirdCard.tsx'

const meta = {
  title: 'ことりずかん/BirdCard',
  component: BirdCard,
  decorators: [
    (Story) => (
      <MemoryRouter>
        <div className="max-w-md bg-sky-50 p-6">
          <Story />
        </div>
      </MemoryRouter>
    ),
  ],
  args: {
    bird: { id: 4, name: 'フクロウ', emoji: '🦉', area: 'もり', rare: 3, memo: 'よるにおきてる' },
    isFavorite: false,
    onToggleFavorite: fn(),
  },
} satisfies Meta<typeof BirdCard>

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
  name: '通常',
}

export const Favorite: Story = {
  name: 'お気に入り済み',
  args: { isFavorite: true },
}

export const NoMemo: Story = {
  name: 'メモなし',
  args: {
    bird: { id: 2, name: 'スズメ', emoji: '🐦', area: 'こうえん', rare: 1, memo: '' },
  },
}

export const LongName: Story = {
  name: '名前が長い',
  args: {
    bird: { id: 1, name: 'キタノモリノエナガ', emoji: '🐤', area: 'もり', rare: 2, memo: 'わざと長い名前' },
  },
}

読み方はこうです。

書くもの意味
export default metaこの部品の共通設定。ここのargs全ストーリーの初期値
export const Default1つのストーリー=1つの状態nameがサイドバーに出る(省くと変数名がそのまま出ます)
args渡すprops。metaargsに上書きされる形で足りない分だけ書く
decoratorsストーリーを包む枠。<Link>を使う部品にはRouterが要る

動かす

npm run storybook

http://localhost:6006が開きます。左のサイドバーにことりずかん/BirdCardが出て、その下に4つの状態が並びます。

見どころは下のパネルです。

パネルできること
Controlspropsをその場で書きかえられる。isFavoriteをFalse/Trueで切りかえるなど
Actionsfn()を渡したpropsがいつ呼ばれたかが出る
Accessibility自動でアクセシビリティ検査。BirdCardViolations 0 / Passes 13 / Inconclusive 1でした

Controlsが効くのは、argsでpropsを渡しているストーリーだけです(部品の中に値を直書きすると効きません)。これは「propsで受け取る」設計を自然に促す仕組みでもあります。

どこで効くのか

正直に言うと、1人で小さいものを作っている間は要りません。効くのはこういう場面です。

場面効き方
状態の組み合わせが多い部品エラー・空・読みこみ中を画面から作らずに見られる
デザイナーやディレクターに見せるbuild-storybookで静的サイトとして配れる。実装環境が要らない
共通部品を作るボタンやモーダルの全パターンが1か所にそろう
レビュー「この状態を見たい」にURLで答えられる
リファクタ部品を書きかえたあと、状態を一巡して見比べられる

逆に、画面をそのまま見れば分かる部品にストーリーを書くのは手間だけです。書くのは「画面から再現しにくい状態がある部品」に絞ります。

npm run build-storybook   # storybook-static/ に書き出される

書き出したものはただの静的サイトなので、公開の回と同じやり方でそのまま置けます。

Reactテストを書く(Vitest + Testing Library)テストの回。Storybookのストーリーは、そのままテストの入力としても使えます

公式ドキュメントも見てみる

Storybook外部サイトStorybook: Frontend workshop for UI developmentStorybook公式の「ストーリーを書く」。metaargsの関係、decoratorsの使い方はここが正確です(英語)

このレッスンのまとめ

  1. Storybookは部品を状態ごとに並べたカタログ。画面から作りにくい状態を固定できる
  2. npx storybook@latest initで設定まで入る。生成されたサンプルは消してよい
  3. Tailwindを使うならpreview.tsxCSSを読ませる
  4. 1ファイル=1部品。export default meta+ストーリーごとのexport
  5. argsが渡すprops。関数のpropsにはfn()を渡すとActionsに記録される
  6. <Link>やContextを使う部品は、decoratorsでProviderに包む
  7. Accessibilityパネルで自動検査が付いてくる(Violations 0を目指す)
  8. 効くのは状態が多い部品・人に見せるとき。全部品に書く必要はない

次は、Tailwind以外のスタイリングです。案件で当たったときに読めるようにします。

やってみよう:BirdCardのカタログを作る

状態を4つ並べます。

  1. npx storybook@latest init
  2. src/stories/のサンプルを削除する
  3. .storybook/preview.tsximport '../src/index.css'を足す
  4. src/components/BirdCard.stories.tsxを作り、通常・お気に入り済み・メモなし・名前が長いの4つを書く
  5. npm run storybookで開き、ControlsでisFavoriteを切りかえてみる
  6. Accessibilityパネルを開いて、違反が0かどうかを見る
npm run storybook

4番でdecoratorsMemoryRouterを外すとどうなるかも、一度見ておいてください。部品が単体で動くかどうかが分かります。

よくある質問

Storybookは個人開発でも役に立ちますか?
役に立つのは、画面から作りにくい状態がある部品です。エラー表示、データが0件のとき、名前が異常に長いとき。こういう見た目を毎回手で再現するより、ストーリーとして固定しておくほうが速くなります。
Storybookとテストは何が違いますか?
Storybookは目で見て確かめるカタログ、テストは機械が確かめる仕組みです。ただ最近のStorybookは同じストーリーを使ってテストも走らせられるので、境目は薄くなっています。
ストーリーはどのくらい書きますか?
その部品が取りうる状態のうち、見た目が変わるものだけです。カードなら通常・お気に入り済み・メモなし・名前が長い、くらい。propsの全組み合わせを書く必要はありません。
Storybookはチームに配れますか?
build-storybookで静的なサイトとして書き出せるので、そのまま社内に置いてデザイナーやディレクターに見てもらえます。実装を触らずに状態を切り替えられるのが利点です。