1. ホーム
  2. React

【React】forwardRef の使い方|親コンポーネントから子のDOMを参照する方法

Share

React で自作のコンポーネントを作っていると、「親から子の中にある input にフォーカスを当てたい」「子のスクロール位置を親から操作したい」といった場面が出てきます。ところが ref はそのままでは自作コンポーネントに渡せず、props として素通しすることもできません。この「子のDOMを親から参照したい」ときに使うのが forwardRef です。この記事では、なぜ forwardRef が必要になるのか、基本の書き方、useImperativeHandle で公開する操作を絞る方法、TypeScript での型付け、そして React 19 での変化まで、初心者向けに解説します。

ref を子コンポーネントに渡せない理由

ref(レフ)は、DOM要素やコンポーネントのインスタンスへの「参照」を保持するための仕組みです。<input ref={inputRef} /> のように素のDOM要素に付ければ、inputRef.current から実際の input 要素にアクセスできます。ところが、自分で作ったコンポーネントに ref を付けても、思ったようには動きません。

not-working.tsx
function MyInput(props) {
  return <input {...props} />;
}

function Form() {
  const inputRef = useRef(null);

  // これは動かない:MyInput は ref を受け取っても内部の input に渡していない
  return <MyInput ref={inputRef} />;
}

refkey と同じく特別な属性で、通常の props には含まれません。そのため MyInput の中で props.ref と書いても取り出せず、{...props} で素通ししても内部の input には届きません。親が渡した ref を「子の内部にある特定のDOMまで転送する」ための橋渡しが必要になります。それを担うのが forwardRef です。

forwardRef の基本の書き方

forwardRef は、コンポーネントを関数で包むことで、第2引数に ref を受け取れるようにします。受け取った ref を内部のDOM要素へそのまま渡すのが基本形です。

MyInput.tsx
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 にフォーカスする、という典型的な例を見てみましょう。

Form.tsx
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>
  );
}

親で作った inputRefMyInput に渡すと、forwardRef がそれを内部の input まで転送します。その結果、inputRef.current は子の input のDOM要素を指し、inputRef.current.focus() でフォーカスを当てられます。親からは MyInput が素のフォーム要素であるかのように ref を扱える、というのが forwardRef のねらいです。

useImperativeHandle で公開する操作を絞る

基本形では、親は子の input のDOMをまるごと受け取ります。つまり value の直接書き換えやスタイル変更など、なんでもできてしまいます。「親には focus() だけ使わせたい」「内部の実装は隠したい」というときは、useImperativeHandle を組み合わせて、親に公開する操作を絞り込めます

FancyInput.tsx
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, () => ({ ... })) は、親から渡された refcurrent に「自分で決めたオブジェクト」を載せる仕組みです。この例では focusclear だけを公開しているので、親からは ref.current.focus()ref.current.clear() しか呼べません。input のDOMをそのまま渡さないため、内部構造を隠しつつ、必要な操作だけを外に見せられます。独自メソッド(例では clear())を用意できるのも利点です。

ただし、こうした「親から子を命令的に操作する」書き方は、React本来の「propsとstateでUIを組み立てる」考え方からは外れます。多用するとデータの流れが追いづらくなるため、フォーカス移動やスクロール、メディアの再生・停止のようにpropsでは表しにくい操作に限って使うのがおすすめです。

TypeScript で forwardRef に型を付ける

TypeScript では、forwardRef2つの型パラメータを渡します。1つ目が ref の指す要素の型、2つ目が props の型です。順番が「ref、props」と直感に反するので注意してください。

TextInput.tsx
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> と書くことで、refinput 要素(HTMLInputElement)を指すものとして扱われ、props には Props 型が適用されます。親側で useRef<HTMLInputElement>(null) と宣言しておけば、ref.current.focus() のような呼び出しも正しく補完・型チェックされます。要素の型は、input なら HTMLInputElementdiv なら 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に渡せます。

react19-input.tsx
// 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 はまだ現役なので、まずは基本の使い方を押さえておきましょう。

参考ページ