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
都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。