Storybookは、ユーザーインタフェースやページの振る舞いを、コンポーネントごとに確かめ、試せるツールです。本稿は、ReactにTypeScriptを組み込んだプロジェクトで、Storybookの使い方について簡単にご説明します。
プロジェクトをつくる
React + TypeScriptのひな形プロジェクトは、Create React Appのつぎのコマンドでつくりましょう(プロジェクト名はreact-typescript-storybook
としました)。TypeScriptを環境に加えるオプションが--template typescript
です(「Create React AppでTypeScriptを使用する」)。
npx create-react-app react-typescript-storybook --template typescript
つぎに、Storybookをインストールします(「Install Storybook」)。つくられたプロジェクト(react-typescript-storybook
ディレクトリ)のルートで、つぎのコマンドを打ち込んでください。
npx storybook init
Storybookの初期画面は、npm run storybook
で開きます(Reactプロジェクトを起動する必要はありません)。URLはhttp://localhost:6006/
です。
サンプルのstoryモジュール(stories.tsx
)やそれらの用いるコンポーネント(tsx
)がつくられ、src/stories
に収められています。実際には、storyモジュールと分けるため、Reactコンポーネントはsrc
下の別フォルダに加えることになるでしょう。
今回は、Reactコンポーネントとしてsrc/stories/Button.tsx
を使います(コードはつぎのとおり)。src/stories/Button.css
がコンポーネントのスタイルを決めるCSSです。本稿では、story(stories.tsx
)以外のモジュールについては、詳しく触れません。
import React from 'react';
import './button.css';
interface ButtonProps {
primary?: boolean;
backgroundColor?: string;
size?: 'small' | 'medium' | 'large';
label: string;
onClick?: () => void;
}
export const Button = ({
primary = false,
size = 'medium',
backgroundColor,
label,
...props
}: ButtonProps) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
return (
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
style={{ backgroundColor }}
{...props}
>
{label}
</button>
);
};
storyを書いてみる
早速、storyを新たなモジュールsrc/stories/HelloButton.stories.tsx
として書きましょう。
ディレクトリはstories
内です。storyのコンポーネントはdefault
でexport
してください(「Default export」)。型はComponentMeta
で定めます。プロパティのtitle
はStorybookの画面左カラムに示されるstory名です。表示するReactコンポーネントをcomponent
に与えます。
名前つきでexport
する関数(HelloButton
)が具体的なstoryの項目(オブジェクト)です(「Named story exports」)。戻り値のJSXがReactコンポーネントの表示を定めます(「Defining stories」参照)。
import { ComponentMeta } from '@storybook/react';
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
} as ComponentMeta<typeof Button>;
export const HelloButton = () => <Button label="Hello!!" />;
Button
コンポーネントが受け取ったプロパティlabel
は、ボタンに示されるテキストです。
export const Button = ({
label,
}: ButtonProps) => {
return (
<button
>
{label}
</button>
);
};
Storybookの画面左カラムにstoryのタイトル「Button」が加わりました。展開すると、名前つきexport
したstoryオブジェクト(項目)の識別子(HelloButton
)がスペース区切りで示されます。なお、storyオブジェクト名には、アッパーキャメルケース(パスカルケース)を使いましょう(「キャメルケース【camel case】」参照)。
Use the named exports of a CSF file to define your component’s stories. We recommend you use UpperCamelCase for your story exports.
(「Defining stories」)
story項目「Hello Button」を選ぶと、Button
コンポーネントはデフォルトのスタイルで表示され、label
のテキストがボタンに示されるでしょう。
ところで、あらかじめサンプルに加えられたstoryは「Example」という名前でグループ化されています。これは、title
をグループ名ではじめ、storyコンポーネント名はそのあとにスラッシュ(/
)区切りで添えたからです(「Grouping」参照。)
export default {
title: 'Example/Button',
component: Button,
} as ComponentMeta<typeof Button>;
storyオブジェクト(項目)を追加する
名前つきexport
するstoryオブジェクト(項目)は、ひとつのstoryコンポーネント(モジュール)にいくつでも加えられます。Button
コンポーネントにはオプションのプロパティがありますので、それを新たなstoryオブジェクトPrimaryLargeButton
に定めましょう。プロパティprimary
はカラーテーマを使う論理値、size
が3段階の大きさです。
import { ComponentMeta } from '@storybook/react';
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
} as ComponentMeta<typeof Button>;
export const HelloButton = () => <Button label="Hello!!" />;
export const PrimaryLargeButton = () => <Button label="Primary Large" primary={true} size="large" />;
どちらのプロパティも、Button
コンポーネントに与えるCSSのクラス(className
)でスタイルを切り替えます。
export const Button = ({
primary = false,
size = 'medium',
}: ButtonProps) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
return (
<button
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
>
</button>
);
};
Storybookでストーリー項目の「Primary Large Button」を選べば、背景色は水色で白抜き文字の大きなボタンが表示されるでしょう。
テンプレートを使う
storyオブジェクトのプロパティはテンプレートのJSXに直に書き込むのでなく、値も含めて分けられれば管理しやすく便利です。その場合、テンプレートのargs
にプロパティを定めます(「Using args」参照)。
まず、テンプレートのコールバックが返すJSXから、具体的なプロパティや値の記述は除いてください。つぎに、各storyオブジェクトにはテンプレートの関数をコピーします。このとき用いるのが、Function.prototype.bind()
メソッドです。
💡
Template.bind({})
is a standard JavaScript technique for making a copy of a function. We copy the Template so each exported story can set its own properties on it.
(「Using args」)
そのうえで、storyオブジェクトのargs
に、プロパティと値をオブジェクトで与えてください。このオブジェクトがテンプレートのコールバックに引数(args
)として渡されます。戻り値のJSXには、引数のプロパティと値を加えればよいのです。
// import { ComponentMeta } from '@storybook/react';
import { ComponentMeta, Story } from '@storybook/react';
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
} as ComponentMeta<typeof Button>;
const Template: Story<typeof Button> = (args) => <Button label='' {...args} />;
// export const HelloButton = () => <Button label="Hello!!" />;
export const HelloButton = Template.bind({});
HelloButton.args = {
label: 'Hello!!'
};
// export const PrimaryLargeButton = () => <Button label="Primary Large" size="large" primary={true} />;
export const PrimaryLargeButton = Template.bind({});
PrimaryLargeButton.args = {
label: 'Primary Large',
primary: true,
size:'large',
}
control
を操作する
テンプレートにargs
を定めると、Storybook下部の[Controls]パネルが使えるようになります。各プロパティについて、args
で与えた以外の値も試せるのです。値のリセットは、パネル右上の[Reset controls]ボタンをクリックしてください。
[Controls]パネルの操作オプションは、カスタマイズできます。そのとき用いるのが、argTypes
です(「ArgTypes」参照)。
Button
コンポーネントのプロパティbackgroundColor
には初期値がありません。選択できるプロパティ値をラジオボタンで[Controls]パネルに加えてみましょう。
export const Button = ({
backgroundColor,
}: ButtonProps) => {
return (
<button
style={{ backgroundColor }}
>
</button>
);
};
argTypes
はstoryコンポーネントに加えます。backgroundColor
に定めるオブジェクトのoptions
が値の配列で、control
は操作方法({ type: 'radio' }
)です。
import { ComponentMeta, Story } from '@storybook/react';
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
argTypes: {
backgroundColor: {
options: ['#1ea7fd', '#e57373', '#ffb74d', 'transparent'],
control: { type: 'radio' },
},
},
} as ComponentMeta<typeof Button>;
const Template: Story<typeof Button> = (args) => <Button label='' {...args} />;
export const HelloButton = Template.bind({});
HelloButton.args = {
label: 'Hello!!'
};
export const PrimaryLargeButton = Template.bind({});
PrimaryLargeButton.args = {
label: 'Primary Large',
size:'large',
primary: true,
}
これで、ボタンの背景色(backgroundColor
)は4つのラジオボタンから選べるようになりました。
なお、control
を{ type: 'select' }
にすれば、オプションをプルダウンメニューから選択できます。
export default {
argTypes: {
backgroundColor: {
// control: { type: 'radio' },
control: { type: 'select' },
},
},
} as ComponentMeta<typeof Button>;
以上が、React + TypeScriptでStorybookをどのように使うかの基本的なご説明です。サンプルのプロジェクトにあらかじめ加えられたstoryについても、モジュールを開いておおむね理解いただけると思います。さらに詳しくは、Storybookの公式ドキュメントをお読みください。