React19はforwardRef絡みの長ったらしい型記述を無くしてくれる

![２枚のカードが並ぶUI。それぞれのカードは薄い緑色で、そのうち1枚にはマウスホバーされていて、より淡い色に変化している。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/f8c16a00-61e7-0c5f-eba9-1b1c0a335ea4.png)



## TL;DR （3行まとめ）

- React 18 までは、TypeScript で `forwardRef` するために、`ref` の型を書いたり、引数の順番・型引数の順番を気にしたり、めんどくさかった
- React 19 では、`forwardRef` が不要になり、普通に `ref` Prop を受け取れる
- TypeScript 型の記述量がかなり減って、書き間違いやすい部分も削減された

## React18までの書き方

React 18 以前では、`ref` Prop を指定できるコンポーネントを書くために、`forwardRef` を使う必要がありました。


- 生の `button` と同じように、属性、`ref` を設定できるカスタマイズ性を保つ
- カスタマイズ性を保ったまま、CSS Modules でスタイルを設定する 

という要件を満たす「`button` をラップしたカードUIコンポーネント」を作成するためには、以下のようなコードを書いていました。

（ちなみに、いわゆる Atomic Design の Atom 相当のコンポーネントをはじめ、汎用的なコンポーネントの多くは、以上の要件をキチンと満たすように実装する必要があります。その理由については、以下の記事を参考にしてください。）

https://zenn.dev/takepepe/articles/atoms-type-definitions

```tsx:card-old.tsx
import {
  type ComponentPropsWithoutRef,
  type ComponentRef,
  forwardRef,
} from "react";
import clsx from "clsx";

import styles from "./card.module.scss";

type RefType = ComponentRef<"button">;
type Props = ComponentPropsWithoutRef<"button">;

export const CardOld = forwardRef<RefType, Props>(function CardOld(
  { className, ...props },
  ref
) {
  return (
    <button ref={ref} {...props} className={clsx(className, styles.root)} />
  );
});
```

<details><summary>card.module.scss の中身はこちら</summary>

```scss:card.module.scss
.root {
  display: grid;
  padding: 16px;

  appearance: none;
  background-color: oklch(from green .93 .06 h);
  border: 1px solid oklch(from green .7 .05 h);
  color: oklch(from green .4 .05 h);
  border-radius: 4px;
  font-size: 1rem;

  cursor: pointer;

  &:hover {
    background-color: oklch(from green .98 .04 h);
  }
}

```
</details>

登場人物を下から順に見ていきましょう

### forwardRef

**`forwardRef<T, P>((props: P, ref: ForwardedRef<T>) => ...)`** は、`ref` を受け取るコンポーネントを作るために（React v18 まで）必要だった関数です。

型引数 `T` は、「ref の中身」のようなものです。`button` タグをラップするコンポーネントを作るなら、 `HTMLButtonElement` を **明示的に** 指定する必要があります。

型引数 `P` は、言わずと知れた Props の型として、 **明示的に** 指定する必要があります。

理由はわかりませんが、型引数は「Ref 関係の型 → Props の型」の順なのに、関数の引数は「`props` → `ref`」の順になっています。この順番は忘れやすいですし、順番を取り違えてもあんまりピンとこないエラーが発生するので、ミスしやすいです。

### ComponentPropsWithoutRef

**`ComponentPropsWithoutRef<Comp>` ユーティリティ型** は、コンポーネント（またはHTMLタグ名の文字列）を引数として受け取って、「そのコンポーネントが受け取る Props の型」を表します。

名前の通り、**`ref` を除いた Props の型** を取得できます。

### ComponentRef

**`ComponentRef<Comp>` ユーティリティ型** は、コンポーネント（またはHTMLタグ名の文字列）を引数として受け取って、「そのコンポーネントの `ref` に渡せる Ref の中身」を表します。

ややこしいので、具体的を挙げてお茶を濁します。

- 文字列を指定した場合、HTML 標準要素に対応する型 
    - `ComponentRef<"button">` の結果は `HTMLButtonElement`
    - `<button ref={ }` ← に渡せる Ref の型は `LegacyRef<HTMLButtonElement>` なので
- コンポーネントそのものの型を指定した場合、そのコンポーネントの受け取れる ref の型
    - `ComponentRef<typeof CardOld>` の結果は、`HTMLButtonElement`

## React19ではforwardRefが不要になって記述が短くなる

React 19 では `forwardRef` なしで同じことができるようになります。

コンポーネントが `ref` Prop を受け取るように書く、ただそれだけで OK です！

```tsx:card-new.tsx
import type { ComponentPropsWithRef, FC } from "react";
import clsx from "clsx";

import styles from "./card.module.scss";

type Props = ComponentPropsWithRef<"button">;

export const CardNew: FC<Props> = ({ className, ...props }) => {
  return <button {...props} className={clsx(className, styles.root)} />;
};
```

しかも、Ref および Props の型を "引用" する方法も少し変わりました。型関連のコードの繰り返しが減ってシンプルになったことが一目瞭然だと思います。

特別な登場人物は1人だけです。

### ComponentPropsWithRef

**`ComponentPropsWithRef<Comp>` ユーティリティ型** は、`ComponentPropsWithoutRef<Comp>` の仲間ですが、こちらは **`ref` も含めた Props** を取得できます。

## React19(RC)の型定義のインストール方法

:::note info
React 19 安定版が、2024/12/05 でリリースされました！なので、以下は気にせず型定義を利用できます！
:::


~~React 19 は、2024/12/01 現在まだ RC なので、`@types/react` が使えず、少し特殊な記述が必要です。~~

```json
{
  "dependencies": {
    "@types/react": "npm:types-react@rc",
    "@types/react-dom": "npm:types-react-dom@rc"
  },
  "overrides": {
    "@types/react": "npm:types-react@rc",
    "@types/react-dom": "npm:types-react-dom@rc"
  }
}
```

https://ja.react.dev/blog/2024/04/25/react-19-upgrade-guide#installing

とりあえず `create-next-app` の v15 を使ってしまえば、コマンド一発でそこらへんの記述を出力してくれるので楽ちんです。 ~~ついでに App Router も楽しんじゃおう！~~

https://nextjs.org/docs/app/api-reference/cli/create-next-app