Storybookの始め方と実装について~FrontNoteよりもいいと思った理由を添えて~

StorybookはUI開発には欠かせないツールとなっていきました。
導入をしたこともなければ、ほとんど開発したこともなかったのでインプットしておきたいと思い学習を開始しました。

:::note info
【こんな方にオススメ！】
・Storybookについて知りたい方
・Storybookの導入を検討している方
:::

Storybookの基本的な書き方を理解して、携わるプロジェクトでの導入を検討してみてください！

## Storybookってなに？
UI開発のためのツールです。
コーディングしたものをアプリケーション上で確認するのが一般的だと思いますが、作成したパーツをアプリケーションを操作することなく確認できるツールです。

https://storybook.js.org/docs/react/get-started/introduction

## 開発環境構築
StotybookのDocsを見ながら導入を進めていきます。

https://storybook.js.org/docs/react/get-started/install

```javascript
npx storybook init
```

installが進むと下記のようなテキストが表示されます。
```
Would you like to send crash reports to Storybook?
Storybook now collects completely anonymous telemetry regarding usage.
This information is used to shape Storybook's roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL https://storybook.js.org/telemetry
```
クラッシュしたときになどにレポートを送信することに対する有無なので「y」と打ち込んで次に進みましょう。

次に`Please choose a project type from the following list`と聞かれるのでどんな形式でStorybookを作成するのか聞かれるので矢印で`react-scripts`を選択してEnterを押します。

:::note warn
installが終わると警告が出ました…なんで？
:::

### どんな警告が出たのか？
`24 high severity vulnerabilities`と表示され続いて修正を実行するように促されます。

```
To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force
```

一番最後に`Do you want to run the 'npm7' migration on your project?`とnpm7に移行を勧められるので大人しく従ってみました。

すると`npm run storybook`がコマンドラインに表示されるので実行してみます。

```
Error: Cannot find module 'react/package.json'
```
errorが出ました…調べてみます。

:::note alert
導入方法が間違っていました…**Storybookの公式にもあるように`storybook init`は空のプロジェクト用に作成されていない**とのことでした…。
:::

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/9549f51c-0394-267c-344b-c706cf4b8a52.png)
引用元：https://storybook.js.org/docs/react/get-started/install

ということでReactのアプリを作成してからStorybookを導入する手順で仕切り直したいと思います！

https://reactjs.org/docs/create-a-new-react-app.html


Reactのドキュメントは新しいものがあるそうなので一応共有しておきます。

https://beta.reactjs.org/

### Reactプロジェクトを作る

`npx create-react-app ./`

:::note info
すでにディレクトリを用意している場合は`npx create-react-app my-app`ではなく`npx create-react-app ./`とすることで現在のディレクトリにReactのプロジェクトを作れます！
:::

構築が終わったので`npm start`を実行すると`http://localhost:3000/`で馴染みのある画面が表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/1398bbfc-8451-59dd-f738-0bc99dacc9a2.png)

`npx storybook init`を実行します。

再びnpm7へ移行することをすすめすすめられたり、セキュリティーの警告を出したりしました。
ただ、npm7へ移行したらStorybookのwelcomページが表示されたのでいったんよしとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/5ab1601e-7fbf-6ec4-355f-01f3191972ce.png)

:::note warn
現時点では警告を解消する場合は手動での構築が必要そうですが、個人開発なので何か問題が起こらないかぎりはこのまま進めたいと思います。
:::

## Storybookが良いと感じている理由
余談になりますが、Storybookがなぜ良いと感じているのかを伝えておきます。
デザインパーツの管理は非常に難しいです。

サイト改善によって同じようなパーツや新しいパーツが生み出され続けます。
それらを**競合なく、誰でも把握できる状態をこれまで作れませんでした…。**

打開策としてGulpでFrontNoteを使いデザインカタログを作成れるので運用し始めましたが、リソースが減るにつれて手が回らなくなり、使われなくなってしまいました。

FrontNoteはscssファイルの中に記述し、コマンドを走らせることで生成してくれます。
FrontNoteに表示させたい部分はバッククォートで囲むことで表示できます。

```scss
/*
#styleguide
button

　```
<button class="button">これはボタンです</button>
　　```

*/
```
FrontNoteが運用にのらなかったのは使いにくかったのだと思います。
複数のパターンがあればどれだけ定義が必要になります。
**増えれば触れるほど記述量が増えるので、運用が大変でした。**

:::note warn
また検索性も悪かったので確認する際の手間も問題でした…。
:::

Storybookは変更できる値を定義しておけばUIで状態や見た目を変更することが可能＆ビジュアルリグレッションテストもできますし、検索もできます。
ツールバーにはさまざまな機能もあるので使い勝手が良いです。

## Storybookの記述方法を確認していく
srcフォルダの下にstoriesがあるので記述はそちらに追加していきます。

:::note info
ドキュメントに沿って作成した場合はcssファイルとjsxファイルを別で作成し、jsxファイルにimportする形で作成されています。
:::

### Cardコンポーネントを作る
jsxファイルとstoriesファイルを作成してStorybookに表示できるところまで実装したいと思います。

```jsx:Card.jsx
import React from 'react';

function Card({ children }) {
  return <div class='card'>{children}</div>;
}

export default Card;
```

```jsx:Card.stories.jsx
import React from 'react';
import Card from './Card';

export default {
  title: 'Example/Card',
  component: Card
};

export const SampleCard = () => <Card>Sample Card!!</Card>;
```
これで下記のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/935de96f-aa9d-6e67-7edc-ced6e758e1d1.png)

:::note warn
なぜか表示されないな…と思ったらtitleの記述が誤っておりました…
表示してほしいヶ所に表示されない場合は一度確認してください。
```diff_jsx
export default {
-  title: 'Card',
+  title: 'Example/Card',
  component: Card
};
```
:::

```jsx:Card.stories.jsx
export const SampleCard = () => <Card>Sample Card!!</Card>;
+ export const DemoCard = () => <Card>Demo Card!!</Card>;
```
`stories.jsx`に新しく定義することで左側のストーリーに追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/c48e3712-e7fb-915f-7d64-460a26dd4597.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/5baf8d60-10b7-d6e7-169f-4b3bb235e0f1.png)

### 引数を設定する
UI上でテキストの変更ができるようにしてみます。
```diff_jsx:Card.stories.jsx
import React from 'react';
import Card from './Card';

export default {
  title: 'Example/Card',
  component: Card
};

- export const SampleCard = () => <Card>Sample Card!!</Card>;
+ const Template = (args) => <Card>{args.text}</Card>;
+ export const SampleCard = Template.bind({});
+ SampleCard.args = {
+   text: 'Sample Card!!'
+ };
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/124a8e23-10d0-0513-8e3d-c17bca2bbf46.png)
Controlsにテキストを入力できる項目が表示され、テキストを打ち込むとCanvasに反映されることが確認できると思います。

### UIでデザインの切り替えができるようにしてみる
まずCardコンポーネントに引数を追加します。
```diff_jsx:Crad.jsx
- function Card({ children }) {
+ function Card({ foundation, children }) {
+ const card = foundation ? 'card-foundation' : 'card';
  return (
    <div class={card}>
      <div class='card-inner'>{children}</div>
    </div>
  );
}
```
CSSを追加します。
```css: card.css
.card-foundation {
  border: 1px solid #ccc;
  border-radius: 2px;
  background-color: #f7f3e8;
}
```
最後にstories.jsxにUI操作のための表示が追加されるように設定します。
```diff_jsx:Card.stories.jsx
const Template = (args) => <Card>{args.text}</Card>;
export const SampleCard = Template.bind({});
SampleCard.args = {
  text: 'Sample Card!!',
+  foundation: false,
};
```
すると画像の通り切り替えボタンが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/a22d9274-4f17-2a31-98b7-acc7d3b75136.png)
`true`にすると背景色が切り替えられるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/fc6a02cf-4e26-7be0-158a-44702c0ead01.png)

### UIで複数の選択しから選んで表示を切り替えられるようにしてみる
`padding`や`font-size`など複数の値から大きさを選びたい場合もあると思います。
そんな場合の実装をしていきます。
Cardコンポーネントで引数の受け取りと`stories.jsx`にて値の設定、CSSを追加します。
```diff_jsx:Crad.jsx
- function Card({ foundation, children }) {
+ function Card({ foundation, children, innerSize }) {
+ const card = foundation ? 'card-foundation' : 'card';
  return (
    <div class={card}>
-      <div class='card-inner'>{children}</div>
+      <div className={`card-inner-${innerSize}`}>{children}</div>
    </div>
  );
}
```
```diff_jsx:Card.stories.jsx
const Template = (args) => <Card>{args.text}</Card>;
export const SampleCard = Template.bind({});
SampleCard.args = {
  text: 'Sample Card!!',
  foundation: false,
+  innerSize: 'medium'
};
```
```css: card.css
.card-inner-small {
  padding: 4px 12px;
}

.card-inner-medium {
  padding: 8px 16px;
}

.card-inner-large {
  padding: 16px 24px;
}
```
画面を確認してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/3c3a3f5a-46ec-655c-4c68-67df138b7468.png)
`innerSize`はラジオボタンで変更したかったのに想像と違っています。
どうやらpropsのtypeを指定する方が良いらしいので、typeを`Crad.jsx`に明記します。

```diff_jsx:Crad.jsx
import React from 'react';
+ import PropTypes from 'prop-types';
import './card.css';

function Card({ foundation, children, innerSize }) {
  const card = foundation ? 'card-foundation' : 'card';
  return (
    <div class={card}>
      <div class={`card-inner-${innerSize}`}>{children}</div>
    </div>
  );
}

export default Card;

+ Card.propTypes = {
+   foundation: PropTypes.bool,
+   innerSize: PropTypes.oneOf(['small', 'medium', 'large']),
+   text: PropTypes.string
+ };
```
画面を確認するとラジオボタンで切り替えられるようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/16db1ad8-5450-d186-2140-591ddd196584.png)

:::note info
値を必須にしたい場合は、`.isRequired`をつけるとできます！
```diff_jsx:Crad.jsx
Card.propTypes = {
  foundation: PropTypes.bool,
  innerSize: PropTypes.oneOf(['small', 'medium', 'large']),
-  text: PropTypes.string
+  text: PropTypes.string.isRequired
};
```
textの横に赤い※印がついたことのが確認できると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/af9790cf-30b9-44ae-f77a-ef921816d9f7.png)

:::

## まとめ
今回はStorybookの導入と基礎的な記述について学んでみました。
今回はCSSファイルをimportする形式でデザインを作成しましたが、本来であれば`styled-components`や`CSS in JS`、`Emotion`などと併用して利用することがほとんどだと思います。

公式サイトを見ると効率的な記述方法なども見受けられました。
知識がまだまだ浅いのは否めませんが、もう少し複雑な要件でコンポーネントを作成するなどでStorybookの知識を高めていきます！

https://storybook.js.org/docs/react/get-started/introduction

さらにビジュアルリグレッションテストなども可能です。
**レガシーなサイトには簡単に導入できないかもしれませんが、自動テストも視野に入れて設計できるとすごく良い**と考えています！