LoginSignup
2
2

【Storybook】

Last updated at Posted at 2023-06-13

Storybookの具体的な活用例

開発者は品質の高いUIの開発と保守ができる

  • コンポーネントのドキュメンテーション
  • コンポーネントのテスト
  • コンポーネントのデザインとスタイリングのプレビュー
  • コンポーネントの再利用と共有

開発者はStorybookを使用してコンポーネントのドキュメンテーションを作成し、テストケースを作成し、リアルタイムでデザインやスタイリングの調整を行い、コンポーネントを再利用し共有することができます。

How to write stories

Component Story Format

The default export metadata controls how Storybook lists your stories and provides information used by addons. For example, here’s the default export for a story file Button.stories.js|ts:

default export : ストーリーファイル全体のメタデータを定義する
コンポーネントのタイトルや表示方法、プロパティの設定など
export const : 個々のストーリーを定義する
特定のコンポーネントの状態に対応するストーリーを記述します。異なるプロパティや引数を持つ複数のストーリーを作成することができる

Storybook 7.0以降では、ストーリーのタイトルはビルドプロセスの一部として静的に解析されます。デフォルトエクスポートには、静的に読み取れるタイトルプロパティが含まれているか、自動的にタイトルが計算できるコンポーネントプロパティが含まれている必要があります。ストーリーのURLをカスタマイズするためにidプロパティを使用する場合も、静的に読み取れる必要があります。

Defining stories

import React from 'react';
import { Button } from './Button';

export const Primary = () => <Button state="primary" />;

この例では、Buttonコンポーネントを<Button state="primary" />としてレンダリングし、"Primary"という名前のストーリーとしてエクスポートしています。このようにして、異なるプロパティや状態を持つ複数のストーリーを定義することができます。

ストーリーファイル内でexport constを使用することで、Storybookはそれぞれのエクスポートを独立したストーリーとして認識します。これにより、複数のストーリーを定義してコンポーネントのさまざまな状態やプロパティを表示することができます。

Working with ReactHooks

React Hooksをストーリーの作成時に使用することは可能ですが、
ストーリーを作成する際は、可能な限りargsを使用することが推奨されている

Button.stories.tsx
import React, { useState } from 'react';
import { Button } from './Button';

export const ButtonWithHooks = () => {
  const [state, setState] = useState('primary');

  const handleClick = () => {
    setState(state === 'primary' ? 'secondary' : 'primary');
  };

  return <Button state={state} onClick={handleClick} />;
};

Button.stories.tsx
import React from 'react';
import { Button } from './Button';

export const ButtonWithHooks = ({ initialState }) => {
  const [state, setState] = useState(initialState);

  const handleClick = () => {
    setState(state === 'primary' ? 'secondary' : 'primary');
  };

  return <Button state={state} onClick={handleClick} />;
};

ButtonWithHooks.args = {
  initialState: 'primary',
};

この例では、ButtonWithHooksコンポーネントの状態をinitialStateという引数で指定します。useStateフックを使って状態を管理し、handleClick関数で状態の切り替えを行います。

ButtonWithHooks.argsを使用して、デフォルトの初期状態を'primary'と設定します。これにより、Storybook上でこのコンポーネントを表示した際にデフォルトの初期状態が設定されます。

このように、argsを使用することでストーリーの作成が柔軟になり、異なる状態やプロパティの組み合わせを簡単に試すことができます。

Rename stories

特定のストーリーの名前を変更するには、該当するストーリーエクスポートの name プロパティを変更します。以下は、"Primary" ストーリーの名前を変更する方法の例です:

import React from 'react';
import { Button } from './Button';

export const Primary = () => <Button state="primary" />;
Primary.storyName = '新しい名前のプライマリストーリー';

この例では、Primary エクスポートの storyName プロパティに新しい値を割り当てています。これにより、ストーリーブックのサイドバーに表示される名前が "新しい名前のプライマリストーリー" に変更されます。storyName プロパティを変更することで、ストーリーに正確で説明的な名前を付けることができます。

Screenshot 2023-06-13 at 12.04.02.png

How to write stories

A story is a function that describes how to render a component. You can have multiple stories per component, and the simplest way to create stories is to render a component with different arguments multiple times.

・.stories.tsxはどこのコンポーネントにおいてもいい
・コンポーネントごとに個別のストーリーを作成することができる(1つのファイル内に複数のストーリーを定義することが可能)

Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  component: Button,
};

export default meta;
type Story = StoryObj<typeof Button>;

/*
 *👇 レンダリング関数は、コンポーネントの表示方法を制御するためのフレームワーク固有の機能です。
 * 詳細については、https://storybook.js.org/docs/react/api/csf
 * を参照してレンダリング関数の使用方法を学びましょう。
 */
export const Primary: Story = {
  render: () => <Button backgroundColor="#ff0" label="ボタン" />,
};

export const Secondary: Story = {
  render: () => <Button backgroundColor="#ff0" label="😄👍😍💯" />,
};

export const Tertiary: Story = {
  render: () => <Button backgroundColor="#ff0" label="📚📕📈🤓" />,
};

Using args

コンポーネントのストーリーに引数を導入して、改良する
ストーリーごとに作成および保守する必要がある定型コードが削減

Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  component: Button,
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    backgroundColor: '#ff0',
    label: 'Button',
  },
};

export const Secondary: Story = {
  args: {
    ...Primary.args,
    label: '😄👍😍💯',
  },
};

export const Tertiary: Story = {
  args: {
    ...Primary.args,
    label: '📚📕📈🤓',
  },
};

argsをコンポーネントのストーリーに導入することのメリット

  • コードの量を減らすだけでなく、Primaryストーリーのargsを他のストーリーに展開することでデータの重複を減らすことができます。

  • 他のコンポーネントのストーリーを書く際にargsをインポートして再利用することができます。

  • 特にコンポジットコンポーネントを構築する際に役立ちます。例えば、ButtonGroupのストーリーを作成する場合、その子コンポーネントであるButtonのストーリーを組み合わせることができます。

Using the play function

コンポーネントのシナリオテストでは以下が用いられる
・@storybook/addon-interactions
・Storybookのplay関数
これらはレンダーごとに実行される小さなスニペットです

例えば、フォームコンポーネントのバリデーションを検証したい場合、次のようなストーリーを書くことができます。このストーリーでは、play関数を使用して入力欄に情報を入力した場合にコンポーネントがどのように応答するかを確認します。

LoginForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { within, userEvent } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import { LoginForm } from './LoginForm';

const meta: Meta<typeof LoginForm> = {
  component: LoginForm,
};

export default meta;
type Story = StoryObj<typeof LoginForm>;

export const EmptyForm: Story = {};

/*
 * See https://storybook.js.org/docs/react/writing-stories/play-function#working-with-the-canvas
 * to learn more about using the canvasElement to query the DOM
 */
export const FilledForm: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // 👇 Simulate interactions with the component
    await userEvent.type(canvas.getByTestId('email'), 'email@provider.com');

    await userEvent.type(canvas.getByTestId('password'), 'a-random-password');

    // See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
    await userEvent.click(canvas.getByRole('button'));

    // 👇 Assert DOM structure
    await expect(
      canvas.getByText(
        'Everything is perfect. Your account is ready and we should probably get you started!'
      )
    ).toBeInTheDocument();
  },
};

Using parameters

パラメータに関する要点を箇条書きでまとめます。

  • パラメータはStorybookのストーリーに静的なメタデータを提供する方法です。
  • ストーリーのパラメータは、ストーリーやストーリーグループのレベルで使用されます。
  • パラメータを使用することで、ストーリーに構成情報を提供することができます。
  • パラメータは、テーマやレスポンシブビューポートの設定など、ストーリーに関連するオプションを指定するために利用されます。
  • パラメータはアドオンと組み合わせて使用することができ、ストーリーブックの柔軟性と拡張性を向上させます。
  • アドオンとの統合により、ビジュアルやインタラクティブな側面をカスタマイズし、より豊かなテスト環境を構築することが可能です。
// Button.stories.ts|tsx

import type { Meta } from '@storybook/react';

import { Button } from './Button';

const meta: Meta<typeof Button> = {
  component: Button,
  //👇 Creates specific parameters for the story
  parameters: {
    backgrounds: {
      values: [
        { name: 'red', value: '#f00' },
        { name: 'green', value: '#0f0' },
        { name: 'blue', value: '#00f' },
      ],
    },
  },
};

export default meta;

image.png

Using decorators

デコレータの使用
デコレータは、ストーリーをレンダリングする際に任意のマークアップでコンポーネントをラップする仕組みです。コンポーネントは、レンダリングされる「場所」に関する前提条件で作成されることが多いです。スタイルにはテーマやレイアウトのラッパーが必要な場合や、UIには特定のコンテキストやデータプロバイダが必要な場合などがあります。

簡単な例として、コンポーネントのストーリーにパディングを追加することを考えてみましょう。以下のように、ストーリーをdivでラップし、パディングを追加するデコレータを使用します。

// Button.stories.ts|tsx

import type { Meta, StoryObj } from '@storybook/react';

import { Button } from './Button';

const meta: Meta<typeof Button> = {
  component: Button,
  decorators: [
    (Story) => (
      <div style={{ margin: '3em' }}>
        {/* 👇 Storybookのデコレータは関数も受け付けます。 <Story/> を Story() で置き換えることで有効にします */}
        <Story />
      </div>
    ),
  ],
};

export default meta;

デコレータはより複雑なものにすることもでき、通常はアドオンによって提供されます。デコレータはストーリー、コンポーネント、グローバルレベルで設定することもできます。

Stories for two or more components

デザインシステムやコンポーネントライブラリを構築する際には、2つ以上のコンポーネントが連携して動作することがあります。例えば、親のListコンポーネントが子のListItemコンポーネントを必要とする場合などです。

この場合、各ストーリーに対して異なる関数をレンダリングすることが意味があります。

// List.stories.ts|tsx

import type { Meta, StoryObj } from '@storybook/react';

import { List } from './List';
import { ListItem } from './ListItem';

const meta: Meta<typeof List> = {
  component: List,
};

export default meta;
type Story = StoryObj<typeof List>;

export const Empty: Story = {};

/*
 *👇 レンダリング関数は、コンポーネントのレンダリング方法を制御するためのフレームワーク固有の機能です。
 * 使用方法については、https://storybook.js.org/docs/react/api/csf
 * を参照してください。
 */
export const OneItem: Story = {
  render: (args) => (
   

 <List {...args}>
      <ListItem />
    </List>
  ),
};

export const ManyItems: Story = {
  render: (args) => (
    <List {...args}>
      <ListItem />
      <ListItem />
      <ListItem />
    </List>
  ),
};

また、Listコンポーネントで子のListItemからストーリーを再利用することもできます。これにより、同じストーリー定義を複数の場所で更新する必要がなくなり、メンテナンスが容易になります。

List.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';

import { List } from './List';
import { ListItem } from './ListItem';

//👇 We're importing the necessary stories from ListItem
import { Selected, Unselected } from './ListItem.stories';

const meta: Meta<typeof List> = {
  component: List,
};

export default meta;
type Story = StoryObj<typeof List>;

/*
 *👇 Render functions are a framework specific feature to allow you control on how the component renders.
 * See https://storybook.js.org/docs/react/api/csf
 * to learn how to use render functions.
 */
export const ManyItems: Story = {
  render: (args) => (
    <List {...args}>
      <ListItem {...Selected.args} />
      <ListItem {...Unselected.args} />
      <ListItem {...Unselected.args} />
    </List>
  ),
};
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2