【React + TypeScript】 Chakra UI の Link と React Router v6 の Link を統合する

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`にまとめます。

```tsx: Link.tsx (最終結果)
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>
  )
}
```


## 考え方

### 公式によると……

```jsx: 公式ドキュメントより転載
// 1. import { Link as ReachLink } from "@reach/router"

// 2. Then use it like this
<Link as={ReachLink} to='/home'>
  Home
</Link>
```

https://chakra-ui.com/docs/components/navigation/link

こちらの公式ドキュメントに載っているものは Reach Router ライブラリの`Link`コンポーネントと統合する方法であることと、JavaScriptのJSXでの記述法になっています。


### 途中経過

この方法にならって、両者の `Link`コンポーネント を `as` を使って `ChakraLink` と `RouterLink` としてインポートし、統合します。
この時点では、当然`props`の型を指定していないため型エラーとなります。

```tsx: Link.tsx (途中経過)
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が生成されます。

```tsx: Div.tsx
import { Box } from "@chakra-ui/react"

export const Div = () => {
  return (
    <Box as="div">
      あいうえお
    </Box>
  )
}
```
```html:表示結果
<div class="css-0">あいうえお</div>
```

そこで、今回のケースを考えたとき、

「`ChakraLink as={RouterLink}`と書いているので、Chakra UIの`Link`がReact Routerの`Link`が扱いになる。
だから、Propsの型にはReact Routerの`Link`用のProps型 `LinkProps` を引数に指定するのが正解なんだろう」

と直感的に思ってしまいました。
しかし、ここに落とし穴があります。

```tsx:Link.tsx (失敗例)
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`)を指定して呼び出すと型エラーになります。

```tsx:Parent.tsx (親コンポーネント)
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型を作る必要があります。

```tsx: Link.tsx (最終結果)
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`の機能を統合したコンポーネントを使えるようになりました。