🐱
Reactで、どんなタグにも変更可能で必要なPropsを推論してくれるジェネリックコンポーネントを作成する
🤔よくある課題
- Headingコンポーネントを特定の箇所ではdtやpタグなどで使用したい
- Buttonコンポーネントをaタグとして使用したいがPropsの定義が面倒
🎁結論
以下のようにすればどんなタグにも変更可能なコンポーネントが作成できます✨
import { ComponentPropsWithoutRef, ElementType } from 'react';
type Props<T extends ElementType> = {
tag?: T;
} & Omit<ComponentPropsWithoutRef<T>, 'tag'>;
export const Button = <T extends ElementType = 'button'>({ tag, ...props }: Props<T>) => {
const Tag = tag || 'button';
return (
<Tag
style={{
// スタイルは適当
backgroundColor: '#000',
display: 'block',
width: '100%',
padding: '16px 24px',
color: '#fff',
fontSize: 14,
}}
{...props}
/>
);
};
これってなに?
下記のように、渡したタグ名によって自動で必要なPropsが推論されます。
もちろん、そのタグに存在しないPropsは型エラーとなります。
⚡️解説
Props
type Props<T extends ElementType> = {
tag?: T;
} & Omit<ComponentPropsWithoutRef<T>, 'tag'>;
上記のPropsでは、以下のようなことをしています。
- Propsで型引数Tを受け取れるようにする
- Tは、ElemetType('button'や、'a'などのHTMLのタグ名)を継承する
- tagの型は、受け取った型引数Tとなるようにする
- 受け取った型引数と、ComponentPropsWithoutRefで、そのタグに紐づいたPropsを推論
tagの型にTを指定することで、tagに渡した値からTの型を推論してくれます。
Component
export const Button = <T extends ElementType = 'button'>({ tag, ...props }: Props<T>) => {
const Tag = tag || 'button';
// ... 省略
デフォルトの型引数とElementTypeを指定すれば、完成です。
以上!
Discussion