ReactでUIライブラリ「Chakra UI 」の Link コンポーネントと ルーティングライブラリ「React Router v6」 の Link コンポーネントを統合したものを、TypeScriptのTSXとして記述する方法をご紹介します。
ライブラリのバージョン
"react": "^17.0.2",
"@chakra-ui/react": "^1.8.7,
"react-router-dom": "6",
結論
React.ComponentProps<typeof コンポーネント名> を使って Chakra UI のLinkの型とReact RouterのLinkの型を割り出して、LinkPropsにまとめます。
import React from "react"
import { Link as ChakraLink } from "@chakra-ui/react"
import { Link as RouterLink } from "react-router-dom"
type LinkProps = React.ComponentProps<typeof ChakraLink> & React.ComponentProps<typeof RouterLink>
export const Link = ({ children, ...props }: LinkProps) => {
return (
<ChakraLink as={RouterLink} {...props}>
{children}
</ChakraLink>
)
}
考え方
公式によると……
// 1. import { Link as ReachLink } from "@reach/router"
// 2. Then use it like this
<Link as={ReachLink} to='/home'>
Home
</Link>
こちらの公式ドキュメントに載っているものは Reach Router ライブラリのLinkコンポーネントと統合する方法であることと、JavaScriptのJSXでの記述法になっています。
途中経過
この方法にならって、両者の Linkコンポーネント を as を使って ChakraLink と RouterLink としてインポートし、統合します。
この時点では、当然propsの型を指定していないため型エラーとなります。
import { Link as ChakraLink } from "@chakra-ui/react"
import { Link as RouterLink } from "react-router-dom"
export const Link = ({ children, ...props }) => { // 'children' is missing in props validation
return (
<ChakraLink as={RouterLink} {...props}>
{children}
</ChakraLink>
//プロパティ 'to' は型 '{ children: any; as: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAnchorElement>>; }'にありませんが、
// 型 'OmitCommonProps<LinkProps & RefAttributes<HTMLAnchorElement>, keyof LinkProps>' では必須です。ts(2741)
)
}
勘違いでPropsの型にハマる
Chakra UI では、コンポーネントのas引数にHTMLタグ名(a,divなど)を指定すると、そのタグ扱いでDOMが生成されます。
import { Box } from "@chakra-ui/react"
export const Div = () => {
return (
<Box as="div">
あいうえお
</Box>
)
}
<div class="css-0">あいうえお</div>
そこで、今回のケースを考えたとき、
「ChakraLink as={RouterLink}と書いているので、Chakra UIのLinkがReact RouterのLinkが扱いになる。
だから、Propsの型にはReact RouterのLink用のProps型 LinkProps を引数に指定するのが正解なんだろう」
と直感的に思ってしまいました。
しかし、ここに落とし穴があります。
import { Link as ChakraLink } from "@chakra-ui/react"
import { Link as RouterLink, LinkProps } from "react-router-dom"
export const Link = ({ children, ...props }: LinkProps) => {
return (
<ChakraLink as={RouterLink} {...props}>
{children}
</ChakraLink>
)
}
この統合されたLinkコンポーネントに Chakra UI コンポーネント用のCSSを設定する引数(以下の場合はfontSize)を指定して呼び出すと型エラーになります。
import { Link } from "components/link"
export const LogInLink: React.FC = () => {
return (
<Link mx="2" fontSize="2rem" to="/auth/login">
ログイン
</Link>
// 型 '{ children: string; mx: string; fontSize: string; to: string; }' を
// 型 'IntrinsicAttributes & LinkProps' に割り当てることはできません。
// プロパティ 'mx' は型 'IntrinsicAttributes & LinkProps' に存在しません。
)
}
これは、統合されたLinkコンポーネントの引数型には、React RouterのLink用のProps型・LinkPropsは指定されているが、Chakra UIのLink用のProps型が含まれていないことが原因です。
よって、両者の引数を受け取れるように、ふたつのLinkコンポーネントのPropsを統合したProps型を作る必要があります。
import React from "react"
import { Link as ChakraLink } from "@chakra-ui/react"
import { Link as RouterLink } from "react-router-dom"
type LinkProps = React.ComponentProps<typeof ChakraLink> & React.ComponentProps<typeof RouterLink>
export const Link = ({ children, ...props }: LinkProps) => {
return (
<ChakraLink as={RouterLink} {...props}>
{children}
</ChakraLink>
)
}
以上で、問題なく外部で両者のLinkの機能を統合したコンポーネントを使えるようになりました。