ロゴテキスト ロゴ

    React/Next.jsにStorybookを導入する

    React/Next.jsにStorybookを導入する

    React or Next.jsのプロジェクトに Storybook を追加で導入します。


    記事内ではNext.jsで進めますが、Storybookの導入はReactでも一緒になります。



    公式のチュートリアルがあるのでそちらを参考にやってみます。 Introduction to Storybook



    GitHubに Next.js + Storybook の参考用として作成したプロジェクトがあるのでよければ参考にしてみて下さい!

    yuki-takara/nextjs-with-storybook

    検証した環境

    1 next 10.1.3
    2 @storybook/react 6.2.2

    導入前の前提

    Next.jsのプロジェクトは以下のようにして作成しています


    TypeScriptだけ導入しておきたかったので--example with-typescriptを指定。

    $ yarn create next-app --example with-typescript nextjs-with-storybook



    この状態で起動し、

    $ yarn run dev
    $ next
    ready - started server on 0.0.0.0:3000, url: http://localhost:3000
    ・・・

    http://localhost:3000/ にアクセスすると以下のように表示されます。

    作成したプロジェクトを開発環境で実行

    試しのコンポーネントを追加

    Storybookで試すためのコンポーネントを追加します

    bgcというPropsを渡す事で背景色を変えられるだけのシンプルなボタン。


    Propsをexportしているのは後ほどのStorybookで使用するためです

    components/LinkButton.tsx
    import React from 'react'
     
    export type LinkButtonProps = {
      bgc?: string
    }
     
    const LinkButton = ({ bgc = '#4752ff' }: LinkButtonProps) => (
      <a style={{backgroundColor: bgc, padding: '10px 30px', color: 'white'}}>ボタン</a>
    )
     
    export default LinkButton
    Storybookで試しに表示するためのコンポーネントをトップページに表示してみる


    Storybookを導入する

    本題です!

    Storybookに必要なパッケージをインストール

    Storybookの導入方法は公式に「Install Storybook」というページがあるためそちらを参考にやっていきます。

    Install Storybook

    Storybook公式のインストールページ



    まずインストールを行うためnpx sb initを実行します。

    $ npx sb init
     
     sb init - the simplest way to add a Storybook to your project.
     ・・・
     ・・・
     
     
       Done in 0.56s.
     
    To run your Storybook, type:
     
       yarn storybook


    インストールが完了すると、様々なパッケージやnpm-scriptsが自動的に追加されます。

    (npm-scriptsも自動なのはありがたい!)


    私の環境だと

    npm-scriptsのコマンドが2つ、Storybookに関するパッケージ+babelが追加されました。

    package.json
    "scripts": {
      "storybook": "start-storybook -p 6006",
      "build-storybook": "build-storybook"
    },
    "devDependencies": {
      "@babel/core": "^7.13.14",
      "@storybook/addon-actions": "^6.2.2",
      "@storybook/addon-essentials": "^6.2.2",
      "@storybook/addon-links": "^6.2.2",
      "@storybook/react": "^6.2.2",
      "babel-loader": "^8.2.2",
    }


    またStorybookの設定ファイル、参考用のファイル群も自動的に追加されます。

    Storybookの設定ファイル、サンプルファイルが追加されている


    npxについての余談

    そもそもnpxてローカルパッケージの中のコマンドを実行するんじゃなかったっけ?

    と思って調べてみたところ

    npxコマンドをたたくと次の順番でコマンドを探します。

    1. ローカルパッケージ(node_modules/.bin)
    2. 環境変数PATH
    3. npmレジストリ

    知らないのは損!npmに同梱されているnpxがすごい便利なコマンドだった | DevelopersIO より引用

    なるほど、今回のケースだと

    ↑で書かれている 「3. npmレジストリ」のコマンドを使用しsb initを実行しているですね!



    Storybookの起動

    先ほど自動で追加されたコマンドを実行します

    $ yarn run storybook


    コマンドを実行すると自動でブラウザが立ち上がります

    Storybookの画面が起動した

    Storybookが見られるようになりました!



    自動でブラウザが立ち上がらない場合は


    $ yarn run storybookが実際に実行している

    $ start-storybook -p 6006-p 以降のポート番号を調べブラウザにアクセスします。


    ↑のデフォルトのケースであれば

    http://localhost:6006/ にアクセスする事で見られます



    Storybookにコンポーネントを表示する

    先ほど作成したコンポーネントをStorybookで表示出来るようにします


    Storybook導入時に参考として入ってるコンポーネントが書き方/使い方共にとても参考になります。


    私がNext.js + Storybookの参考用に作成したリポジトリに残しているので良ければ参考にしてみて下さい。

    https://github.com/yuki-takara/nextjs-with-storybook/blob/master/stories/Button.stories.tsx



    Storybook用のファイルを作成

    Storybookに表示するためには、

    Storybookに表示するコンポーネントとは別にStorybook専用のファイルを作ります



    「LinkButton.tsx」と同じ階層に「LinkButton.stories.tsx」を作成します。

    同じ階層にStorybook用のファイルを作成する


    Storybookのファイル名はコンポーネント名.stories.tsxとするのが慣習なためそれに準じています。


    ファイルの作成場所は、最終的にファイルのパスを.storybook/main.jsで指定するだけなのでどこでも問題ありません。


    私の印象だとコンポーネントと同階層に配置するケースが多いイメージなので、

    ここでは同じ階層に作っていきます。




    LinkButton.stories.tsxの内容は以下のようにしました

    (コードが難しいですね…後ほど紐解いていきます)

    LinkButton.stories.tsx
    import React from 'react'
    import { Story, Meta } from '@storybook/react'
     
    import LinkButton, { LinkButtonProps } from './LinkButton'
     
    export default {
      title: 'My/Button',
      component: LinkButton
    } as Meta
     
    const Template: Story<LinkButtonProps> = (args) => <LinkButton {...args} />
     
    export const DefaultButton = Template.bind({})
    DefaultButton.args = {}


    次に「.storybook/main.js」に作成したファイルを読み込む設定を追記します

    .storybook/main.js
    module.exports = {
      "stories": [
        "../stories/**/*.stories.mdx",
        "../stories/**/*.stories.@(js|jsx|ts|tsx)",
        // ↓の行を追加
        "../components/**/*.stories.@(js|jsx|ts|tsx)"
      ],
      "addons": [
        "@storybook/addon-links",
        "@storybook/addon-essentials"
      ]
    }

    これで$ yarn run storybookでStorybookを立ち上げてみると

    Storybook上でサンプル用として作成したコンポーネントが表示された

    コンポーネントがStorybookに表示されました!



    stories.tsx/jsxの書き方などは公式ドキュメントがかなりキレイにまとまっているため、

    つまづいた時などオススメです!

    How to write stories



    ここからはLinkButton.stories.tsxに書いた内容を見ていきます





    Storybookに表示する大まかな設定(メタデータ)を書く

    公式の下記ページ(英語)が分かりやすくまとまっています

    https://storybook.js.org/docs/react/api/csf#default-export



    まずexport defaultを用いて

    Storybookのサイドバーに表示する内容、そして使用するコンポーネントなどメタデータを定義します

    The default export defines metadata about your component, including the component itself, its title (where it will show up in the navigation UI story hierarchy), decorators, and parameters.


    デフォルトのエクスポートでは、コンポーネント自体、そのタイトル(ナビゲーションUIストーリー階層に表示される場所)、デコレーター、パラメーターなど、コンポーネントに関するメタデータが定義されます。(Google日本語訳)


    Component Story Format (CSF) より引用

    export default {
      title: 'My/Button',
      component: LinkButton
    } as Meta

    プロパティ内容
    titleStorybookのサイドバーに表示されるタイトル
    component使用するコンポーネント

    他にも指定出来るプロパティは色々ありますが、

    まず titlecomponentを抑えておければStorybookに表示/操作が行えるようになります。





    title/を用いる事で階層を作る事が出来ます。


    コードを追っていくとExampleとしてtitle: 'Design System/Atoms/Button'と書かれていて、

    これを実際に試してみると

    階層が作られる

    「Atoms」というフォルダが切られています!

    コンポーネントが増えて時、グループ分けしたい際にはぜひ使用したい機能ですね ☺️




    コンポーネントの設定

    こちらも公式が分かりやすくまとめてくれています

    https://storybook.js.org/docs/react/api/csf#named-story-exports



    次にexport constを使い、Storybook上に実際に描画されるコンポーネントの設定を記載します

    // 1. Storybookで描画するためのコンポーネントの雛形を用意しておく
    const Template: Story<LinkButtonProps> = (args) => <LinkButton {...args} />
     
    // 2. bindを用いて雛形を元にしたコピーを作成
    export const DefaultButton = Template.bind({})
    // 3. Propsに値を設定しない
    DefaultButton.args = {}


    Storybookに複数のコンポーネントを表示する場合

    例えば、背景色を赤くするコンポーネントも合わせて表示する場合以下のように書きます

    const Template: Story<LinkButtonProps> = (args) => <LinkButton {...args} />
     
    // Propsを何も渡さないボタン
    export const DefaultButton = Template.bind({})
    DefaultButton.args = {}
     
    // Propsを用いて背景色を赤くしたボタン
    export const RedButton = Template.bind({})
    RedButton.args = {
      bgc: '#f00'
    }
    Propsで値を渡して赤い背景色のボタンを描画する



    型定義・bindと難しい箇所が多いですね、、

    bindを使っている理由は関数の定義をいちいちしなくて良くするため、です。


    もしbindを使わずに書く場合、

    export const DefaultButton: Story = () => <LinkButton />
    export const RedButton: Story<LinkButtonProps> = (args: LinkButtonProps) => <LinkButton bgc={args.bgc} />
    RedButton.args = {
      bgc: '#f00'
    }

    このように描画するコンポーネント毎に関数を書く必要が出てきます。



    今回のようにコンポーネントが2つだけ、Propsも少ない場合はいいですが、

    これ以上に記載が必要になってくるようであればbindを使った方が可読性良くキレイに書けます。



    Storybookでの表示をカスタマイズする(Optional)

    最後にここまでの設定から更にStorybookを便利に利用する方法をご紹介します


    アドオンのcontrolsの操作を変更する

    StorybookにはStorybookの機能を拡張し便利にするアドオンというものがあります。



    $npx sb initをした際にそのアドオンでよく使われるものをひとまとめにした @storybook/addon-essentialsというパッケージが自動的にインストールされて使える状態になっています!

    (ありがたや!!)

    https://storybook.js.org/docs/react/essentials/introduction




    その中の1つにControlsというコンポーネントの振る舞いをStorybook上で確認出来る非常に便利なアドオンがあります。





    現状私が先ほど追加したコンポーネントの背景色のコンポーネント部分は、

    色の指定が文字列になっている

    特に何も設定していないため文字列を指定するだけのものになっています。



    これをカラーピッカーに変更するにはexport defaultしている部分のargTypesに指定します

    LinkButton.stories.tsx
    export default {
      title: 'Design System/Atoms/Button',
      component: LinkButton,
      argTypes: {
        bgc: { control: 'color' },
      },
    } as Meta
    カラーピッカーが使えるようになった




    他にもセレクトボックスやラジオボタンなど色々な選択方法があり、

    使い方も含め公式ページに細かく記載されています

    https://storybook.js.org/docs/react/essentials/controls#annotation



    例えば、先ほどの色の選択を無理矢理セレクトボックスを使って以下のようにする事も出来ます

    LinkButton.stories.tsx
    export default {
      title: 'Design System/Atoms/Button',
      component: LinkButton,
      argTypes: {
        bgc: {
          options: ['#f00', '#0f0', '#00f'],
          control: { type: 'select' }
        },
      },
    } as Meta



    storybookに表示される名前を変更する

    表示するコンポーネントの storyNameに名前を設定する事でStorybookで表示される名前を変更する事が出来ます

    LinkButton.stories.tsx
    export const DefaultButton = Template.bind({})
    DefaultButton.args = {}
    DefaultButton.storyName = 'デフォルトのボタン'
     
    export const RedButton = Template.bind({})
    RedButton.args = {
      bgc: '#f00'
    }
    RedButton.storyName = '赤いボタン'
    storyNameを使ってStorybookに表示される名前を変更出来る



    描画するコンポーネントを別コンポーネントやタグでラップする

    Decoratorsを使います

    https://storybook.js.org/docs/react/writing-stories/decorators



    Decoratorsを使うことで

    • 別のコンポーネントをimportしてラップする
    • divタグで囲んでstyleを当てた上で表示する

    といった事が出来ます。




    例えば、背景色を黒くした上に表示した場合を確認する、といった事も簡単に実現出来ます

    LinkButton.stories.tsx
    export const DefaultButton = Template.bind({})
    DefaultButton.args = {}
    DefaultButton.decorators = [
      Story => (
        <div
          style={{ backgroundColor: '#000', height: '100px', display: 'flex', alignItems: 'center' }}
        >
          <Story />
        </div>
      ),
    ]
    Decoratorsを使ってコンポーネントに
    プロフィールの背景画像 プロフィール画像
    Yuki Takara
    都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。