Reactで任意のタグ名を設定できるコンポーネントを作る
前書き
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
を受け取ります。as
は button
や a
など色々考えられるので 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