🐋

Reactで任意のタグ名を設定できるコンポーネントを作る

2021/07/27に公開

前書き

as を受け取るコンポーネントはどういうコンポーネントかというと、Chakra UI の Box コンポーネント にいい例があるのですが、

<Box as="button" borderRadius="md" px={4} h={8}>
  Button
</Box>

上記のようなコンポーネントです。

このようなコンポーネントを自作する場合に、型をどう付与するかについて書きます。

作ったもの:CodeSandbox

作成する目標コンポーネント

<Custom text="Hello World" as="a" href="/top" />

コードとしては上記のように as を受け取り、as で指定したエレメントが持つ属性を設定できるようにします。
as で指定したエレメントが持つ属性を設定できるようにというのは、例えば、a タグとして使用する場合には、href属性を受け取ることができます。さらに指定したタグの属性以外は受け取れないようにします。一例として、button タグとして使用する場合に href 属性を与えようとするとエラーになるようなコンポーネントを作成します。

適切なPropsの型検査を目指す

as で指定したエレメントにない属性を設定しようとするとエラーが出ており、目的としたコンポーネントを作成できていると言えるかと思います。

作成

先に完成形を掲載します。

type OwnProps<E extends React.ElementType> = {
  text: string;
  as?: E;
};

type Props<E extends React.ElementType> = OwnProps<E> &
  Omit<React.ComponentProps<E>, keyof OwnProps<E>>;

export const Custom = <E extends React.ElementType = "div">({
  text,
  as
}: Props<E>) => {
  const TagName = as || "div";

  return <TagName>{text}</TagName>;
};

export default function App() {
  return (
    <div className="App">
      <Custom text="Hello World" as="a" href="/top" />
    </div>
  );
}


上から説明していくと、
まずはコンポーネント自体が受け取るPropsからタグが受け取る属性を除いたPropsを定義します。今回はテキストを表示するためtextとレンダリングされるエレメントを設定するasを受け取ります。asbuttona など色々考えられるので Generics を用いて、後から設定できるようにしておきます。

type OwnProps<E extends React.ElementType> = {
  text: string;
  as?: E;
};

次に、

type Props<E extends React.ElementType> = OwnProps<E> &
  Omit<React.ComponentProps<E>, keyof OwnProps<E>>;

コンポーネントの props を定義します。

この Props が何をしているかというと、
E というエレメントが受け取る全ての props から、先ほど定義した OwnProps(textとasが含まれる)を削除し、その型と OwnProps を Union で合体させています。
こうすることで、タグ E が受け取ることができる Props を保持しつつ、コンポーネントの Props も設定できます。

export const Custom = <E extends React.ElementType = "div">({
  text,
  as
}: Props<E>) => {
  const TagName = as || "div";

  return <TagName>{text}</TagName>;
};

用意した Props を使用するとコンポーネントはこのようになり、今回はデフォルトタグとして div を設定しています。

<Custom text="Hello World" as="a" href="/top" />

使用する場合は、div タグ以外を指定する場合は as props に値を渡し、属性も設定したい場合は設定します。

使用例(aタグとして使用した場合)

as で指定したエレメントにない属性を設定しようとするとエラーが出ており、目的としたコンポーネントを作成できていると言えるかと思います。

まとめ

Generic を用いた自作コンポーネントについて上手く型をつけたいという記事を書きました。
書いていて思ったのですが、as に Union Type でとりうるタグ名を設定してもいいかもしれません。

Discussion