現場で使えるReactコンポーネント第1弾 Button編
はじめに
はじめまして、仕事でWebエンジニアをしている者です。
開発の初期段階で作成したコンポーネントが開発の終盤になって使いにくいなと感じたことはありませんか?
コンポーネントを作成したときは十分に使えていても、のちに例外的な使い方やデザインが必要となることが多々あると思います。
多少な例外にも対応できる汎用的で使いやすいコンポーネントを作って現場で活躍しましょう!!
Button.tsx
今回は、汎用コンポーネントの中でも誰もが最初に作るであろうButtonを作成します。
開発環境は以下の通りです。
node.js v18.14.2
react v18.2.0
typescript v4.9.5
今回作ったボタンはこんな感じ
実際に作成したコードがこちらです↓↓
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はこんな感じ
使いやすさを重視したポイントは以下の通りです。
- propsは
ButtonHTMLAttributes
を拡張する - Buttonのラベルは
children
を使う - スタイルの指定は文字列リテラルをユニオン型で結合した型で受け取る
- 親からもスタイルを変更できるようにする
- 汎用コンポーネントでは
margin
を指定しない
ButtonHTMLAttributes
を拡張する
1.propsはButtonHTMLAttributes<HTMLButtonElement>
はHTMLのボタンタグが持つ属性を許容するために指定しています。
そうすることで通常のbuttonタグと同様に親からdisabled
やtype="submit"
などの属性を渡すことができます。
そして、25行目のスプレッド構文{...props}
で受け取ったpropsをそのままbuttonタグに渡しています。
ButtonHTMLAttributes<HTMLButtonElement>
には、children
やclassName
も含まれています。
children
を使う
2. Buttonのラベルはフロントの開発経験がある人なら当たり前だと思いますが、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を与えるようにしましょう!
この記事を読めば、その理由がわかります。
最後に
今回の記事で作成したコードはGithubにて公開しています。
また、この開発ではstorybookを利用しており、storybookはデプロイしてあるので遊んでみてください。
こんな画面です。
今後も別のコンポーネントについて執筆したいと思います。
最後まで読んでいただきありがとうございました。
Discussion