React で自作のコンポーネントを作っていると、「親から子の中にある input にフォーカスを当てたい」「子のスクロール位置を親から操作したい」といった場面が出てきます。ところが ref はそのままでは自作コンポーネントに渡せず、props として素通しすることもできません。この「子のDOMを親から参照したい」ときに使うのが forwardRef です。この記事では、なぜ forwardRef が必要になるのか、基本の書き方、useImperativeHandle で公開する操作を絞る方法、TypeScript での型付け、そして React 19 での変化まで、初心者向けに解説します。
目次
ref を子コンポーネントに渡せない理由
ref(レフ)は、DOM要素やコンポーネントのインスタンスへの「参照」を保持するための仕組みです。<input ref={inputRef} /> のように素のDOM要素に付ければ、inputRef.current から実際の input 要素にアクセスできます。ところが、自分で作ったコンポーネントに ref を付けても、思ったようには動きません。
function MyInput(props) {
return <input {...props} />;
}
function Form() {
const inputRef = useRef(null);
// これは動かない:MyInput は ref を受け取っても内部の input に渡していない
return <MyInput ref={inputRef} />;
}
ref は key と同じく特別な属性で、通常の props には含まれません。そのため MyInput の中で props.ref と書いても取り出せず、{...props} で素通ししても内部の input には届きません。親が渡した ref を「子の内部にある特定のDOMまで転送する」ための橋渡しが必要になります。それを担うのが forwardRef です。
forwardRef の基本の書き方
forwardRef は、コンポーネントを関数で包むことで、第2引数に ref を受け取れるようにします。受け取った ref を内部のDOM要素へそのまま渡すのが基本形です。
import { forwardRef } from "react";
// 第1引数が props、第2引数が親から渡された ref
const MyInput = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
export default MyInput;
ポイントは、forwardRef に渡す関数が (props, ref) という2つの引数を取ることです。通常のコンポーネントは props だけを受け取りますが、forwardRef で包むと、React が親から渡された ref を第2引数として届けてくれます。あとはその ref を内部の input に付けるだけで、親からその input にアクセスできるようになります。
親側では useRef で参照を作り、それを子に渡します。ボタンを押したら子の input にフォーカスする、という典型的な例を見てみましょう。
import { useRef } from "react";
import MyInput from "./MyInput";
function Form() {
const inputRef = useRef(null);
const handleClick = () => {
// 子コンポーネント内部の input にアクセスできる
inputRef.current.focus();
};
return (
<div>
<MyInput ref={inputRef} placeholder="お名前" />
<button onClick={handleClick}>入力欄にフォーカス</button>
</div>
);
}
親で作った inputRef を MyInput に渡すと、forwardRef がそれを内部の input まで転送します。その結果、inputRef.current は子の input のDOM要素を指し、inputRef.current.focus() でフォーカスを当てられます。親からは MyInput が素のフォーム要素であるかのように ref を扱える、というのが forwardRef のねらいです。
useImperativeHandle で公開する操作を絞る
基本形では、親は子の input のDOMをまるごと受け取ります。つまり value の直接書き換えやスタイル変更など、なんでもできてしまいます。「親には focus() だけ使わせたい」「内部の実装は隠したい」というときは、useImperativeHandle を組み合わせて、親に公開する操作を絞り込めます。
import { forwardRef, useImperativeHandle, useRef } from "react";
const FancyInput = forwardRef((props, ref) => {
// 内部で実物の input を参照する ref を別に持つ
const innerRef = useRef(null);
// 親の ref に「公開したい操作だけ」を載せる
useImperativeHandle(ref, () => ({
focus: () => {
innerRef.current.focus();
},
// clear() のような独自の操作も公開できる
clear: () => {
innerRef.current.value = "";
},
}));
return <input ref={innerRef} {...props} />;
});
export default FancyInput;
useImperativeHandle(ref, () => ({ ... })) は、親から渡された ref の current に「自分で決めたオブジェクト」を載せる仕組みです。この例では focus と clear だけを公開しているので、親からは ref.current.focus() と ref.current.clear() しか呼べません。input のDOMをそのまま渡さないため、内部構造を隠しつつ、必要な操作だけを外に見せられます。独自メソッド(例では clear())を用意できるのも利点です。
ただし、こうした「親から子を命令的に操作する」書き方は、React本来の「propsとstateでUIを組み立てる」考え方からは外れます。多用するとデータの流れが追いづらくなるため、フォーカス移動やスクロール、メディアの再生・停止のようにpropsでは表しにくい操作に限って使うのがおすすめです。
TypeScript で forwardRef に型を付ける
TypeScript では、forwardRef に2つの型パラメータを渡します。1つ目が ref の指す要素の型、2つ目が props の型です。順番が「ref、props」と直感に反するので注意してください。
import { forwardRef } from "react";
type Props = {
label: string;
placeholder?: string;
};
// forwardRef<ref が指す要素の型, props の型>
const TextInput = forwardRef<HTMLInputElement, Props>((props, ref) => {
return (
<label>
{props.label}
<input ref={ref} placeholder={props.placeholder} />
</label>
);
});
export default TextInput;
forwardRef<HTMLInputElement, Props> と書くことで、ref は input 要素(HTMLInputElement)を指すものとして扱われ、props には Props 型が適用されます。親側で useRef<HTMLInputElement>(null) と宣言しておけば、ref.current.focus() のような呼び出しも正しく補完・型チェックされます。要素の型は、input なら HTMLInputElement、div なら HTMLDivElement のように、対象のDOMに合わせて指定します。
よく使う型パラメータの対応を、下の表にまとめておきます。
| 書き方 | 意味 |
|---|---|
forwardRef<HTMLInputElement, Props> | ref が input 要素を指し、props は Props 型 |
forwardRef<HTMLDivElement, Props> | ref が div 要素を指す場合 |
forwardRef<HTMLButtonElement, Props> | ref が button 要素を指す場合 |
React 19 では ref を普通の prop として渡せる
React 19 からは、関数コンポーネントで ref を通常の props の1つとして受け取れるようになりました。forwardRef で包まなくても、props から直接 ref を取り出して内部のDOMに渡せます。
// React 19:forwardRef で包まず、props から ref を受け取れる
function MyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
この変更により、新しく書くコードでは forwardRef が不要になる場面が増えました。とはいえ、既存のプロジェクトやライブラリでは forwardRef がまだ広く使われており、React 18 以前を使う環境も多く残っています。そのため、まずは forwardRef の基本を理解しておくことが大切です。React 19 に移行したあとで ref を prop として渡す書き方に置き換えていく、と考えるとよいでしょう。
まとめ
forwardRef は、親から渡された ref を子コンポーネントの内部にあるDOM要素まで転送するための機能です。ref は通常の props には含まれないため、自作コンポーネントで ref を受け取るには forwardRef((props, ref) => ...) の形で包み、内部の要素に ref を渡します。公開する操作を絞りたいときは useImperativeHandle を組み合わせ、TypeScript では forwardRef<要素の型, Propsの型> と型を付けます。React 19 では ref を普通の prop として渡せるようになりましたが、forwardRef はまだ現役なので、まずは基本の使い方を押さえておきましょう。