🖲️

現場で使えるReactコンポーネント第1弾 Button編

2023/03/24に公開

はじめに

はじめまして、仕事でWebエンジニアをしている者です。

開発の初期段階で作成したコンポーネントが開発の終盤になって使いにくいなと感じたことはありませんか?
コンポーネントを作成したときは十分に使えていても、のちに例外的な使い方やデザインが必要となることが多々あると思います。

多少な例外にも対応できる汎用的で使いやすいコンポーネントを作って現場で活躍しましょう!!

Button.tsx

今回は、汎用コンポーネントの中でも誰もが最初に作るであろうButtonを作成します。

開発環境は以下の通りです。

node.js v18.14.2
react v18.2.0
typescript v4.9.5

今回作ったボタンはこんな感じ

ボタンのサンプル
実際に作成したコードがこちらです↓↓

Button.tsx
import { FC, ButtonHTMLAttributes } from "react";
import classNames from "classnames";
import styles from "./Button.module.css";

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  size?: "small" | "medium" | "large";
  visual?: "primary" | "secondary" | "alert";
}

const Button: FC<ButtonProps> = ({
  size = "medium",
  visual = "primary",
  children,
  className,
  ...props
}) => {
  return (
    <button
      className={classNames(
        styles.button,
        styles[size],
        styles[visual],
        className
      )}
      {...props}
    >
      {children}
    </button>
  );
};

export default Button;

cssはこんな感じ
https://github.com/shuuuu10-01/useful-react-components/blob/main/src/components/Button/Button.module.css#L1-L61

使いやすさを重視したポイントは以下の通りです。

  1. propsはButtonHTMLAttributesを拡張する
  2. Buttonのラベルはchildrenを使う
  3. スタイルの指定は文字列リテラルをユニオン型で結合した型で受け取る
  4. 親からもスタイルを変更できるようにする
  5. 汎用コンポーネントではmarginを指定しない

1.propsはButtonHTMLAttributesを拡張する

ButtonHTMLAttributes<HTMLButtonElement> はHTMLのボタンタグが持つ属性を許容するために指定しています。
そうすることで通常のbuttonタグと同様に親からdisabledtype="submit"などの属性を渡すことができます。
そして、25行目のスプレッド構文{...props}で受け取ったpropsをそのままbuttonタグに渡しています。

ButtonHTMLAttributes<HTMLButtonElement> には、childrenclassNameも含まれています。

2. Buttonのラベルはchildrenを使う

フロントの開発経験がある人なら当たり前だと思いますが、Buttonのラベルはchildrenを使うようにしましょう。

私が初心者時代によくやっていたのが、propsでlabelを渡して表示する方法。
❌以下、のちに後悔する実装(めっちゃ端折ってます)

interface Props {
  label: string
}
const Button: FC<ButtonProps> = ({ label }) => {
  return (
    <button>
      {label}
    </button>
  );
};
export default Button;

もし、このように実装した場合、ボタンにアイコン+文字を表示させたいときに困ります(実体験)。

今回のbuttonタグに限らず、propsで受け取ったものをそのまま表示させたい場合は、childrenを使うことをおすすめします。

3. スタイルの指定は文字列リテラルをユニオン型で結合した型で受け取る

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  size?: "small" | "medium" | "large";
  visual?: "primary" | "secondary" | "alert";
}

size?: "small" | "medium" | "large";
visual?: "primary" | "secondary" | "alert";
TypeScriptなのでもし無効な文字列を渡そうとした場合、エラーとなります。また補完が効くのでストレスフリーにpropsを渡すことができます。
また、この文字列リテラルと同じクラス名でcssを記述することで受け取った値をそのままcssのクラスに当てはめることができ、開発もスムーズです。

4. 親からもスタイルを変更できるようにする

propsでclassName?: stringを受け取り、それをclassNameに渡すだけです。
これを行うことで親からcssクラスを指定することができ、微妙なデザインの追加することができます。

5. 汎用コンポーネントではmarginを指定しない

フロント開発をしたことのある人なら当たり前だと思うかもしれませんが、汎用的なコンポーネントでmarginを指定するのはやめてください。
親側でコンポーネントを配置する際にmarginを与えるようにしましょう!
この記事を読めば、その理由がわかります。
https://qiita.com/otsukayuhi/items/d88b5158745f700be534

最後に

今回の記事で作成したコードはGithubにて公開しています。
https://github.com/shuuuu10-01/useful-react-components

また、この開発ではstorybookを利用しており、storybookはデプロイしてあるので遊んでみてください。
こんな画面です。

https://shuuuu10-01.github.io/useful-react-components/?path=/docs/button--docs

今後も別のコンポーネントについて執筆したいと思います。
最後まで読んでいただきありがとうございました。

Discussion