ReactのforwardRefはなぜ必要で、いつ使うべきか

## はじめに
実務でまたしても何をしているのかわからないコードに出会いました。
初めましての`forwardRef`です。

この記事では、実務で出てきたこちらのコードの意味を理解することをゴールとします。また調べる中で生まれた疑問を元に、forwardRefをいつ使うべきかについての推測も記載します。
```typescript:TextInput.tsx
import { ComponentProps,forwardRef } from 'react'

export type TextInputProps = ComponentProps<'input'>

export const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props, ref) => {
  return <input ref={ref} {...props} />
})
```
先に結論を書きます。

:::note info
- 自分で定義したコンポーネントにはrefを渡せない
- `forwordRef`を使うと、自分で定義したコンポーネントにもrefを渡せるようになる。これにより、親コンポーネントから子コンポーネントのDOM要素を操作できるようになる
:::


## 自分で定義したコンポーネントにrefを渡せないらしい
React公式ドキュメントを調べてみました。
> `<input />`のようなブラウザ要素を出力する組み込みコンポーネントに ref を置いた場合、React はその ref の`current`プロパティを、対応する DOM ノード（ブラウザの実際の`<input />`など）にセットします。

例として「[テキスト入力フィールドにフォーカスを当てる](https://ja.react.dev/learn/manipulating-the-dom-with-refs#example-focusing-a-text-input)」が公式に挙げられています。
refでDOMを操作するのは何度か経験しました。

問題は、[別のコンポーネントのDOMノードにアクセスする](https://ja.react.dev/learn/manipulating-the-dom-with-refs#accessing-another-components-dom-nodes)場合です。

> ただし、独自のコンポーネント、例えば`<MyInput />`に ref を置こうとすると、デフォルトでは`null`が返されます。

> デフォルトでは React は、コンポーネントが他のコンポーネントの DOM ノードにアクセスできないようにしているためです。自分自身の子でさえもです！ 

例として挙げられているソースコードで、コンソールにWarningが出ているのが確認できます。
`<MyInput ref={inputRef} />`がダメってことみたいですね。
```console
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
```
ボタンをクリックしてhandleClickを実行してみると以下のエラーが発生しました。
```
App.js: Cannot read properties of null (reading 'focus') 
```
`inputRef.current`がnullだからfocusできないよーと言われているようです。
>別のコンポーネントの DOM ノードまで手動で操作できてしまうと、コードがさらに壊れやすくなってしまいます。

Reactがあえてこの仕様にしているようです。
Reactはrefを「避難ハッチ」としています。こんな風に書くということは、あんまり使ってほしくないんでしょうね🧐

## forwordRefを使うと自分で定義したコンポーネントにrefを渡せる
別のコンポーネントからrefを渡してDOM操作をしたい場合もありますよね。そこで使われるのが、`forwordRef`です。
> 代わりに、内部の DOM ノードを意図的に公開したいコンポーネントは、そのことを明示的に許可する必要があります。コンポーネントは、自身が受け取った ref を子のいずれかに「転送 (forward)」するよう指定できます。

```javascript
const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});
```
`forwardRef`を使うと、親コンポーネントから子コンポーネントの要素にrefを渡せるようになります。

無制限に全てのコンポーネントのDOM要素を操作できたら、どこの何の要素なのか分からなくなって混乱しそうです。`forwardRef`を使う場合は、`forwardRef`で公開した特定のDOM要素だけ、他のコンポーネントからアクセスできるようになるから安全でしょ？？的なニュアンスかなと思いました。

## forwordRefに型をつける
TypeScriptで型をつけると以下のようになります。
[React TypeScript Cheatsheetの例](https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/forward_and_create_ref/)を載せます。
```typescript: 
import { forwardRef, ReactNode } from "react";

interface Props {
  children?: ReactNode;
  type: "submit" | "button";
}
export type Ref = HTMLButtonElement;

export const FancyButton = forwardRef<Ref, Props>((props, ref) => (
  <button ref={ref} className="MyClassName" type={props.type}>
    {props.children}
  </button>
));
```

`forwordRef`はジェネリクスで型を指定します。
```typescript
forwardRef<RefType, PropsType>
```
- RefType: 転送するrefの型（この場合Ref(=`HTMLButtonElement`)）
- PropsType: コンポーネントが受け取るpropsの型（この場合`Props`）

RefTypeを指定することで、このコンポーネントに渡せるrefの型を`HTMLButtonElement`に限定します。これにより、`<div>`や`<input>`などの他の要素のrefを誤って渡すことを防ぐことができます。

これでより安全にコンポーネント間でrefを受け渡すことができます。

## わからなかったコードを振り返る
ここまで調べれば、実務でわからなかったコードを解読できそうです。
```typescript:TextInput.tsx
import { ComponentProps,forwardRef } from 'react'

export type TextInputProps = ComponentProps<'input'>

export const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props, ref) => {
  return <input ref={ref} {...props} />
})
```
`<TextInput>`コンポーネントは`forwardRef`を使用して定義されています。これにより、親コンポーネントからrefを受け取り、内部の<input>要素に転送できます。
これにより、親コンポーネントからDOM操作ができるようになるというわけですね。

例えばこんな感じで、`App.tsx`で定義したrefを`<TextInput>`コンポーネントに渡します。Focus the inputをクリックすると`handleClick`が実行され、`<input>`要素にフォーカスされます。

https://codesandbox.io/embed/sz5ctl?view=editor+%2B+preview&module=%2Fsrc%2FApp.tsx

## 疑問
`forwardRef`を調べて、わからなかったコードの意味は理解できたのですが疑問が生まれました。
**「私今まで何度もrefをpropsで渡してきたよな？🧐」** と。

調べてみると、「自分で定義したコンポーネントにrefを渡せない」のではなく「自分で定義したコンポーネントに**refという名前でpropsを渡せない**らしい」です。

例えば、以下のような場合エラーは起きず、問題なく動きます。

```typescript
import React, { useRef, RefObject } from 'react';

type TextInputProps = ComponentProps<'input'> & {
  inputRef: RefObject<HTMLInputElement>;
};

// <TextInput>は`inputRef`という名前でrefをpropsとして受け取る
function TextInput({ inputRef, ...props }: TextInputProps) {
  return <input {...props} ref={inputRef} />;
}

export default function MyForm() {
  const inputRef = useRef<HTMLInputElement>(null);

  function handleClick() {
    inputRef.current?.focus();
  }

  return (
    <>
      {/* 親コンポーネントは定義したrefを`inputRef`という名前のpropsで渡す */}    
      <TextInput inputRef={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}
```
forwardRefを使わなくてもrefを渡せてしまっていますよね。
propsの名前を変えるだけでrefを渡せるなら、forwardRefいらないんじゃないか？？？

## いつforwardRefを使うか考えてみる
ここからは私の推測です。（間違っている可能性があります🙇）

propsの名前を変えるだけでrefを渡せる仕様から、Reactでは通常のpropsとしてrefを渡すことを技術的には禁止していないことがわかります。

別のコンポーネントからDOM操作させたくないReactが、refをpropsで渡す仕様をあえて残したのは、refがDOM操作をするためだけに使われるものではないからだと思います。

[refは値を参照する](https://ja.react.dev/learn/referencing-values-with-refs)ことにも使われます。
> コンポーネントに情報を「記憶」させたいが、その情報が新しいレンダーをトリガしないようにしたい場合、ref を使うことができます。

例えば、前回の状態やタイマーIDなど、再レンダーに影響を与えない情報を保持できます。
このときstateのように、propsとして複数のrefに名前をつけて別コンポーネントに渡す可能性もあります。どんな場合でもpropsとしてrefを渡すことを禁止してしまったら、困る人が急増しそうです。

単純な値を参照したいときにpropsとしてrefを渡せるようにこの仕様を残したのではないでしょうか。

Reactコンポーネントにはrefをわざわざpropsで定義しなくても渡せるようになっているので、内部的に特別なpropsとして扱われていると思われます。

ユーザーがrefという名前で、Reactが管理するDOM要素へアクセスするようなrefをpropsで渡せてしまうとしたら、内部実装とぶつかってしまう気がします。

よって、単純な値を参照したいときは`forwardRef`を使わずにpropsとしてrefという名前以外でrefを渡し、DOM操作をしたいときは`forwardRef`を使ってrefを転送するという使い分けがいいのではないかと思いました。

まとめるとこんなイメージ。

:::note info
- forwardRefを使うとき
親コンポーネントから子コンポーネントの内部DOM要素を操作したいとき

- propsとしてrefを渡すとき
親コンポーネントから子コンポーネントへ、値の参照や状態の保持など、再レンダーに影響を与えない情報を渡したいとき。refという名前を避けて他の名前で渡す
:::


## 終わりに
わからなかったソースコードの解読をするために`forwardRef`を調べたら、疑問が生まれてしまいました…
[いつforwardRefを使うか考えてみる](#いつforwardRefを使うか考えてみる)については、私の推測なので間違っている可能性があります🙇
補足や指摘事項等ありましたらコメントいただけると嬉しいです！！