前言#
React
において、ref
と forwardRef
はすべての状況で必須ではありません。 React
は宣言的な方法で UI
を構築することを奨励しており、props
と state
を通じてビューの更新を駆動します。 ref
と forwardRef
はより命令的な操作に属し、通常は DOM
操作やコンポーネントインスタンスへのアクセスが必要な場合に使用されます。
使用シーン#
どのような場合に 必須 (または非常に必要 / 強く推奨) で ref
と forwardRef
を使用するべきか:
1. DOM
要素を直接操作する:
これは ref
の最も主要かつ古典的な適用シーンです。 React
の宣言的な世界では、通常 DOM
を直接操作することは避けます。しかし、いくつかのシーンでは、必須または直接操作 DOM
がより便利で効率的です:
- フォーカス管理 (Focus Management):
- 特定の要素にフォーカスを当てる: 例えば、フォームが読み込まれた後に最初の入力ボックスに自動的にフォーカスを当てる、またはモーダルが開いた後に最初のインタラクティブな要素にフォーカスを当てる。
- フォーカスを失ったときに操作を実行する: 例えば、入力ボックスがフォーカスを失ったときに検証をトリガーする。
- テキスト選択 (Text Selection) と操作:
- プログラム的に入力ボックス内のテキストを選択する: 例えば、ボタンをクリックした後に入力ボックス内のすべてのテキストを自動的に選択する。
- テキストボックスのカーソル位置を取得または設定する。
- メディア要素の制御 (Media Element Control):
<video>
または<audio>
要素の再生、停止、音量などを制御する: 例えば、カスタムのビデオプレーヤー制御を実現する。- メディア要素のイベントをリッスンする: 例えば、ビデオ再生終了イベントをリッスンする。
- Canvas 要素操作:
- Canvas 要素のコンテキスト (2D または WebGL) を取得して描画操作を行う。
- サードパーティの
DOM
操作ライブラリを統合する:- 直接
DOM
を操作するサードパーティライブラリを統合する必要がある場合 (現代の React アプリケーションではこのようなケースは少なくなっていますが)、例えば古い jQuery プラグインや、直接DOM
要素にアクセスする必要があるチャートライブラリなど。
- 直接
DOM
要素のネイティブメソッドをトリガーする:- 例えば、input 要素の
focus()
,blur()
,click()
,select()
などのメソッドをトリガーする。
- 例えば、input 要素の
例:入力ボックスにフォーカスを当てる
import React, { useRef, useEffect } from 'react';
function MyForm() {
const inputRef = useRef(null);
useEffect(() => {
// コンポーネントがマウントされた後、input 要素にフォーカスを当てる
inputRef.current.focus();
}, []);
return (
<div>
<input type="text" ref={inputRef} placeholder="入力してください..." />
{/* ... 他のフォーム要素 ... */}
</div>
);
}
2. 子コンポーネントの DOM
ノードにアクセスする (必要な場合は forwardRef
):
デフォルトでは、親コンポーネントは ref
を通じて子コンポーネントの DOM
ノードに直接アクセスすることはできません。 親コンポーネントで子コンポーネントの DOM
要素を取得したい場合 (例えば、子コンポーネントがカスタムの Input コンポーネントで、親コンポーネントがその内部の <input>
要素を取得する必要がある場合)、 forwardRef
を使用して ref を子コンポーネント内部の DOM
要素に渡す必要があります。
なぜ forwardRef
が必要なのか?
これは ref
がデフォルトでコンポーネントインスタンス (クラスコンポーネントの場合) または DOM
要素にのみバインドできるためです。 関数型コンポーネントの場合、デフォルトでは ref
はコンポーネント自体にバインドされますが、関数型コンポーネント自体にはインスタンスがないため、ref
は通常 null
になります。 forwardRef
を使用すると、親コンポーネントから渡された ref
を子コンポーネントの特定の DOM
要素またはクラスコンポーネントインスタンスに転送できます。
例:親コンポーネントが子コンポーネント Input の DOM
要素を取得する
import React, { useRef, forwardRef, useImperativeHandle, useEffect } from 'react';
// 子コンポーネント - カスタム Input コンポーネント
const CustomInput = forwardRef((props, ref) => {
const inputElementRef = useRef(null);
// useImperativeHandle を使用して子コンポーネントのメソッドを公開する (オプション、非 forwardRef 必須)
useImperativeHandle(ref, () => ({
focus: () => {
inputElementRef.current.focus();
},
getValue: () => {
return inputElementRef.current.value;
}
}));
return (
<div>
<label htmlFor={props.id}>{props.label}: </label>
<input type="text" id={props.id} ref={inputElementRef} {...props} />
</div>
);
});
CustomInput.displayName = 'CustomInput'; // React DevTools に表示しやすくするため
// 親コンポーネント
function ParentComponent() {
const customInputRef = useRef(null);
useEffect(() => {
// コンポーネントがマウントされた後、CustomInput コンポーネント内部の input 要素にフォーカスを当てる
customInputRef.current.focus();
// CustomInput コンポーネント内部の input の値を取得する (useImperativeHandle で公開されたメソッドを通じて)
// const inputValue = customInputRef.current.getValue();
// console.log("Input Value:", inputValue);
}, []);
return (
<div>
<CustomInput label="名前" id="nameInput" ref={customInputRef} />
{/* ... 他の内容 ... */}
</div>
);
}
3. 特定のコンポーネントライブラリやシーンでコンポーネントインスタンスにアクセスする必要がある ( useImperativeHandle
と forwardRef
を組み合わせて):
React はデータ駆動を推奨していますが、いくつかの状況では、親コンポーネントで命令的に子コンポーネントのメソッドを呼び出す必要があるかもしれません。例えば:
- 子コンポーネントの内部状態や動作を制御する: 例えば、ポップアップコンポーネントの
open()
またはclose()
メソッドをトリガーする、またはテーブルコンポーネントのrefreshData()
メソッドを呼び出す。 - 特定のサードパーティ UI コンポーネントライブラリを統合する: 一部のコンポーネントライブラリは命令的な API を提供する場合があり、メソッドを呼び出すために ref を通じてコンポーネントインスタンスを取得する必要があります。
useImperativeHandle
を使用して親コンポーネントに公開する API を制御する:
親コンポーネントが ref を通じて子コンポーネントのメソッドにアクセスできるようにする必要がある場合、通常は forwardRef
と useImperativeHandle
フックを組み合わせて使用します。 useImperativeHandle
を使用すると、どのメソッドやプロパティが ref を通じて親コンポーネントに公開されるかを正確に制御でき、すべての内部詳細を公開することを避けることができます。
ref
と forwardRef
の使用を避けるべき場合:
- すべては宣言的な方法 (props と state) を優先すべきです。 できるだけ props を通じてデータやコールバック関数を渡して子コンポーネントの動作や状態を制御します。
- コンポーネント間の通信に
ref
を過度に使用することを避ける。 過剰なref
の使用はコンポーネントの関係を複雑にし、メンテナンスが難しくなり、React のデータフローを破壊します。 - 子コンポーネントの state にアクセスするために
ref
を使用しない。 データを props を通じて渡し、子コンポーネントがデータをコールバック関数を通じて親コンポーネントに渡す (状態の持ち上げ) べきです。 - 明確な必要性がない限り、
forwardRef
を乱用しない。 親コンポーネントが子コンポーネントのDOM
またはインスタンスに直接アクセスする必要がない場合は、forwardRef
を使用する必要はありません。
まとめ:
ref
と forwardRef
は React における強力なツールですが、慎重に使用する必要があります。 それらは以下の状況で必要または非常に有用です:
DOM
要素を直接操作する必要がある場合 (例えば、フォーカス管理、メディア制御など)。- 親コンポーネントが子コンポーネントの
DOM
ノードにアクセスする必要がある場合 (forwardRef
を使用)。 - 特定のシーンで命令的に子コンポーネントのメソッドを呼び出す必要がある場合 (
forwardRef
とuseImperativeHandle
を使用)。
宣言的な方法で問題を解決することを優先してください。 宣言的な方法が実現困難であるか、命令的な操作がより効率的で簡潔な場合にのみ、ref
と forwardRef
の使用を検討してください。 それらの過度な使用を避け、コードの明確さとメンテナンス性を保ちましょう。