🐱

Reactで、どんなタグにも変更可能で必要なPropsを推論してくれるジェネリックコンポーネントを作成する

2023/04/22に公開

🤔よくある課題

  • 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