Storybookチュートリアル ノート

# 概要

この記事はStorybookのチュートリアルを進めた時の記録です。

StorybookはUIコンポーネントをカタログ化して管理できるようになるツール。
アプリケーションやサーバーを起動することなく、StorybookのGUIでコンポーネントを確認しながら作成していくことが可能。

* storybook

https://storybook.js.org/

* チュートリアル

https://storybook.js.org/tutorials/intro-to-storybook/react/ja/get-started/


* チュートリアルの完成形

1. アプリケーション
![スクリーンショット 2021-03-12 15.19.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/802246/4dd4f1ea-f8c9-e119-5c1e-0256abf2cf3d.png)
2. storybook
![スクリーンショット 2021-03-12 15.04.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/802246/d74c0ba5-0cbe-cc83-48f8-fd56033570d0.png)

* Storybook画面について

Reactコンポーネントのリストが画面左に表示されています。
Taskコンポーネント以下の「Default」、「Pinned」、「Archived」というのはそれぞれタスクの状態に名前を付けたものです。
ただ閲覧するだけでなく、入力フォームからパラメータを変更することで即座にプレビューが表示されます。(リロードするとリセットされます。)


# Reactアプリケーションの作成

```sh
npx create-react-app storybook-tutorial
```

# Storybookの追加

```sh
npx -p @storybook/cli sb init
```

参考:
[https://www.npmjs.com/package/@storybook/cli]

> In addition to init, the CLI also has other commands:

> add - add an addon and register it
> info - print out system information for bug reports
> migrate - run codemods to migrate your code
> See the command-line help with -h for details.

## コンポーネント駆動設計 (CDD)

UIの開発プロセスの1つ。
コンポーネントから作り始めて、最後に画面を作り上げる。
UI作成における複雑性を軽減できる。

https://www.componentdriven.org/

## ストーリーファイル

Storybookにコンポーネントを認識させるための設定ファイル。
コンポーネントの状態を定義しexportすることにより、画面からプレビューとして確認することができるようになる。

story: 特定の状態で描画された要素を返す関数

### コンポーネントの定義をexport

```js
export default {
  component: Task,
  title: 'Task'
};
```

### コンポーネントの状態をexport

```js

const Template = args => <Task {...args} />;
export const Default = Template.bind({});

// 状態のデフォルト値
Default.args = {
  task: {
    id: '1',
    title: 'Test Task',
    state: 'TASK_INBOX',
    updatedAt: new Date(2018, 0, 1, 9, 0),
  }
};

// 状態1
export const Pinned = Template.bind({});
Pinned.args = {
  task: {
    ...Default.args.task,
    state: 'TASK_PINNED',
  }
};

// 状態2
export const Archived = Template.bind({});
Archived.args = {
  task: {
    ...Default.args.task,
    state: 'TASK_ARCHIVED',
  }
};

```

Template.bind({}): 関数のコピー作成

## preview.js

```js

import '../src/index.css';

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
}
```

アドオンの振る舞いを設定する。
actionsアドオン: 画面クリック時に情報を表示するためのアドオン。

## PropTypes

Reactコンポーネントへの引数の型チェック。
React15.5から別パッケージとして切り出された。
パフォーマンス上の理由から開発モードでのみ動作させる。

https://ja.reactjs.org/docs/typechecking-with-proptypes.html

尚、TypeScriptやFlowを利用している場合、それらの型チェックで代用できる。
※ ビルド後のコンポーネントに手を入れることがある場合等、PropTypesを利用していればビルドの環境によらず型制約をかけられる、という意見がある。よって、TypeScriptであってもPropTypesを使うという選択肢もあり得る。


## スナップショットテスト

StorybookによりUIコンポーネントを状態毎に目視で確認しながら開発できるが、Storyshotsアドオンを使うとスナップショットテストを自動化できる。

```sh
yarn add -D @storybook/addon-storyshots react-test-renderer
```

テストを追加

```js
// src/storybook.test.js

import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();
```

テストの実行

```sh
$ yarn test

 PASS  src/storybook.test.js
  Storyshots
    Task
      ✓ Default (6 ms)
      ✓ Pinned (1 ms)
      ✓ Archived

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   3 passed, 3 total
Time:        2.057 s
Ran all test suites related to changed files.
```


「src/\_\_snapshots\_\_/storybook.test.js.snap」にスナップショットファイルが作成される。

```js
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Storyshots Task Archived 1`] = `
<div
  className="list-item"
>
  <label
    className="checkbox"
  >
    <input
      defaultChecked={true}
      disabled={true}
      name="checked"
      type="checkbox"
    />
    <span
      className="checkbox-custom"
      onClick={[Function]}
    />
  </label>
  <div
    className="title"
...
```
  

## デコレータについて

Storyにおいて、プレビュー時にのみコンポーネント要素のラッパ要素が欲しくなる場合がある。
以下のリンクで解説されていて、画像も載っているが、例えばボタンコンポーネントの周りにマージンを表示したい、等。

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

デコレータは以下のように設定する。

```js
export default {
  component: TaskList,
  title: "TaskList",
  decorators: [story => <div style={{ padding: '3rem'}}>{story()}</div>],
};
```

## ユニットテスト

StoryをDOMに描画し、検索をかけることでUIのユニットテストが可能。
よって、テストの際にはStoryの実装を再利用できる。

ただし、UIは変更が多いのでユニットテストのメンテは難しい。
Storybook上での確認、スナップショットテスト、リグレッションテストに頼ったほうがよい。

```js
// TaskList.test.js

import React from "react";
import ReactDOM from 'react-dom';
import '@testing-library/jest-dom/extend-expect';

import { WithPinnedTasks } from "./TaskList.stories";

it('renders pinned tasks at the start of the list', () => {
  const div = document.createElement('div');
  ReactDOM.render(<WithPinnedTasks {...WithPinnedTasks.args} />, div);

  const lastTaskInput = div.querySelector('.list-item:nth-child(1) input[value="Task 6 (pinned)"]');
  expect(lastTaskInput).not.toBe(null);

  ReactDOM.unmountComponentAtNode(div);
});
```

テスト実行

```sh
$ yarn test

 PASS  src/components/TaskList.test.js
  ✓ renders pinned tasks at the start of the list (20 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.57 s, estimated 1 s
Ran all test suites related to changed files.
```