万能なコンポーネントを作りたい
投稿日: 2025年01月06日
オリアプ第一弾でコードレビューしていただく中でコンポーネントの汎用性を上げる型をたくさん教えていただいて、いろんなところで応用できるなと思ったので、Qiitaで記事を書いていたのですが、こちらでも共有しておいた方がいいかなと思ったので大事なところだけ抜粋してまとめてみます。
Buttonのコンポーネントです。
基本デザインは複数パターンがあるけど、いろんなPropsを受け取れるようにするための手法です。
import { ReactNode, ComponentPropsWithRef, forwardRef } from "react";
type Variant =
| "outlined"
| "contained-blu"
| "contained-gry"
| "contained-blu500";
interface Props extends Omit<ComponentPropsWithRef<"button">, "className"> {
variant?: Variant;
children?: ReactNode;
}
export const Button = forwardRef<HTMLButtonElement, Props>(
({ variant, children, ...props }, ref) => {
const className = () => {
switch (variant) {
case "outlined":
return "border-solid border-2 border-slate-600";
case "contained-blu":
return "bg-custom-blue";
case "contained-gry":
return "bg-gray-300";
case "contained-blu500":
return "bg-blue-500 text-white";
default:
return "";
}
};
return (
<button
ref={ref}
{...props}
className={`w-full h-full rounded-full ${className()}`}
>
{children}
</button>
);
}
);
Button.displayName = "Button";
下記のように記述するとbuttonタグが持つすべての属性をpropsにすることができる型です。
ComponentPropsWithRef<"button">
refを許容したかったのでComponentPropsWithRef
を使いましたが、許容したくない場合はComponentPropsWithoutRef
を使います。
使う属性だけ列挙していかなくてもこれだけでまるっと指定できるので便利です。
特定の属性だけを除去できる型です。
今回はスタイルを統一するためにコンポーネントをつくったので、classNameを渡されては困ります。ということでclassNameを除去しました。
Omit<ComponentPropsWithRef<"button">, "className">
背景色はpropsでvariantを用意して変えられるようにしています。
今回はパターン4つでvariantで指定するため、Omitで対応しました。
より汎用性を上げて、スタイルも親コンポーネントで自由に操作できるようにしようと考えたらpropsにclassNameを用意しないと不具合生じることがあるようです。
親コンポーネントから子コンポーネントにref
を渡すための仕組みです。
refを渡せるようにしていなかったら(forwardRefを使わなかったら)子コンポーネントのインスタンスに直接アクセスできないので、例えばフォーカスの設定やアニメーションの制御、フォームバリデーション等を別途状態管理などを行って操作しないといけないということになります。
ボタンのコンポーネントっていくつかパターンがあったり、少しだけ変えたいみたいなこともあるので汎用性が上がるとより便利にコンポーネントが扱えると思います。
そのため改めてShiftBブログでも取り上げてみました。
参考になれば嬉しいです。