3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

このままやったらフロントエンドエンジニアになれへんで!Advent Calendar 2022

Day 24

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

Last updated at Posted at 2022-12-23

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

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

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

Storybookってなに?

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

開発環境構築

StotybookのDocsを見ながら導入を進めていきます。

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を押します。

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が出ました…調べてみます。

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

image.png
引用元:https://storybook.js.org/docs/react/get-started/install

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

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

Reactプロジェクトを作る

npx create-react-app ./

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

構築が終わったのでnpm startを実行するとhttp://localhost:3000/で馴染みのある画面が表示されました。
image.png

npx storybook initを実行します。

再びnpm7へ移行することをすすめすすめられたり、セキュリティーの警告を出したりしました。
ただ、npm7へ移行したらStorybookのwelcomページが表示されたのでいったんよしとします。
image.png

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

Storybookが良いと感じている理由

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

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

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

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

/*
#styleguide
button

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

*/

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

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

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

Storybookの記述方法を確認していく

srcフォルダの下にstoriesがあるので記述はそちらに追加していきます。

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

Cardコンポーネントを作る

jsxファイルとstoriesファイルを作成してStorybookに表示できるところまで実装したいと思います。

Card.jsx
import React from 'react';

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

export default Card;
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

なぜか表示されないな…と思ったらtitleの記述が誤っておりました…
表示してほしいヶ所に表示されない場合は一度確認してください。

export default {
-  title: 'Card',
+  title: 'Example/Card',
  component: Card
};
Card.stories.jsx
export const SampleCard = () => <Card>Sample Card!!</Card>;
+ export const DemoCard = () => <Card>Demo Card!!</Card>;

stories.jsxに新しく定義することで左側のストーリーに追加されます。
image.png
image.png

引数を設定する

UI上でテキストの変更ができるようにしてみます。

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
Controlsにテキストを入力できる項目が表示され、テキストを打ち込むとCanvasに反映されることが確認できると思います。

UIでデザインの切り替えができるようにしてみる

まずCardコンポーネントに引数を追加します。

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を追加します。

card.css
.card-foundation {
  border: 1px solid #ccc;
  border-radius: 2px;
  background-color: #f7f3e8;
}

最後にstories.jsxにUI操作のための表示が追加されるように設定します。

Card.stories.jsx
const Template = (args) => <Card>{args.text}</Card>;
export const SampleCard = Template.bind({});
SampleCard.args = {
  text: 'Sample Card!!',
+  foundation: false,
};

すると画像の通り切り替えボタンが表示されます。
image.png
trueにすると背景色が切り替えられるようになりました。
image.png

UIで複数の選択しから選んで表示を切り替えられるようにしてみる

paddingfont-sizeなど複数の値から大きさを選びたい場合もあると思います。
そんな場合の実装をしていきます。
Cardコンポーネントで引数の受け取りとstories.jsxにて値の設定、CSSを追加します。

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>
  );
}
Card.stories.jsx
const Template = (args) => <Card>{args.text}</Card>;
export const SampleCard = Template.bind({});
SampleCard.args = {
  text: 'Sample Card!!',
  foundation: false,
+  innerSize: 'medium'
};
card.css
.card-inner-small {
  padding: 4px 12px;
}

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

.card-inner-large {
  padding: 16px 24px;
}

画面を確認してみます。
image.png
innerSizeはラジオボタンで変更したかったのに想像と違っています。
どうやらpropsのtypeを指定する方が良いらしいので、typeをCrad.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

値を必須にしたい場合は、.isRequiredをつけるとできます!

Crad.jsx
Card.propTypes = {
  foundation: PropTypes.bool,
  innerSize: PropTypes.oneOf(['small', 'medium', 'large']),
-  text: PropTypes.string
+  text: PropTypes.string.isRequired
};

textの横に赤い※印がついたことのが確認できると思います。

image.png

まとめ

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

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

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

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?