React forwardRefの仕組みと使い方をやさしく解説

はじめに

Reactを触っていると、コンポーネント同士のデータのやり取りやDOM要素へのアクセスについて気になることはありませんか。 とくにカスタムコンポーネントを作成していると、子コンポーネント内部のDOM要素へ直接アクセスしたい場合に、どのように参照を受け渡すべきか悩む方が多いようです。 そのようなケースで便利なのが、React forwardRef という機能です。

この機能をうまく活用すると、コンポーネントをまたいだDOM要素へのアクセスが簡単になり、外部ライブラリとの組み合わせにも応用できます。 実務のシーンでも、日々の開発をスムーズにする手助けになるでしょう。

とはいえ、最初は「なぜ普通に props で渡すだけではいけないのか」「どんなときに forwardRef を使えばいいのか」という疑問が出てきませんか。 そこでこの記事では、React初心者の皆さんにもわかりやすいように、React forwardRef の基本的な考え方や具体的なコード例を丁寧に解説していきます。

この記事を読むとわかること

Reactのコンポーネント同士で参照(ref)を受け渡す必要があるシーンは、意外と多いかもしれません。 ここでは、React forwardRef を中心に、どんな使い方をすればいいのかを学んでいきます。

  • forwardRef の基本的な役割としくみ
  • 実務で想定される具体的な利用例
  • ほかの ref 関連機能(useRef など)との違い
  • forwardRef を活用する際のメリットと気をつけたい点

これらを理解できれば、自分が作ったコンポーネントを外部から呼び出すときに、DOMの操作をスムーズにしたり、外部ライブラリとの相性を考慮しながらより柔軟な実装を行ったりできるようになります。 どうぞ最後まで読んでみてください。

Reactでのrefのおさらい

まず、ref とは何かを簡単に振り返ってみましょう。 React では、コンポーネント内のDOM要素に直接アクセスしたいときや、外部ライブラリで要素を操作する必要があるときに、ref を使います。

たとえば、下記のようにシンプルなフォームで入力要素へフォーカスを当てたい場面を想像してください。 通常は useRef を使ってコンポーネント内部で要素を参照し、イベントハンドラなどで ref.current.focus() のようにフォーカスを当てます。

import React, { useRef } from "react";

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

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <input type="text" ref={inputRef} placeholder="テキストを入力" />
      <button onClick={handleFocus}>フォーカスを当てる</button>
    </div>
  );
}

export default SimpleForm;

この例では、1つのコンポーネント内で完結しているため、useRef で十分です。 ところが、入力用のコンポーネントを別ファイルに切り分けて再利用したい場合に、「子コンポーネントからDOM要素を親に渡したい」シチュエーションが出てきます。 このとき、単純に props で要素を渡すだけではうまくいかないことがあります。

ここで活躍するのが React forwardRef です。 forwardRef を使うと、子コンポーネントのDOM要素への参照を親コンポーネントに直接渡せます。

forwardRefとは何か

React forwardRef は、関数コンポーネントやクラスコンポーネントをラップする高階コンポーネント(Higher-Order Component)の一種です。 これを使うことで、子コンポーネント内部で設定した ref を親コンポーネントに橋渡しするようなイメージになります。

たとえば、普通の関数コンポーネントは以下のように書きます。

function MyComponent(props) {
  // 何らかの処理
  return <div>コンポーネントの中身</div>;
}

しかし、これを forwardRef で囲むと、実行時の引数として ref が受け取れるようになるのが特徴です。 こうすることで、従来ならコンポーネント外からアクセスできなかった内部要素を、ref オブジェクトを介して操作しやすくなります。

もう少しわかりやすく、簡単な例を見てみましょう。

import React, { forwardRef } from "react";

function InnerButton(props, ref) {
  return <button ref={ref}>クリックしてください</button>;
}

const ForwardedButton = forwardRef(InnerButton);

export default ForwardedButton;

forwardRef を使わない場合、props の他に ref という引数を直接受け取ることはできません。 しかしこのように記述することで、InnerButton の第二引数として ref が使えるようになります。 これにより、コンポーネント外でこの ForwardedButton を使うときに、実際の <button> へアクセスできるのです。

なぜforwardRefが必要なのか

React では基本的に、子コンポーネントは親コンポーネントから受け取った props やコンテキストを使って内容をレンダリングします。 通常、子コンポーネント内部のDOM要素に直接アクセスするというのは、「カプセル化の観点から望ましくない」とされる場合もあります。

しかし実務では、どうしても子コンポーネント内部のDOM操作を外部から行いたいときがあります。 たとえば以下のようなシーンです。

フォーム要素へのフォーカス制御

ダイアログが表示されたときに特定の入力欄に自動でフォーカスを当てる。

外部ライブラリとの連携

たとえば外部ライブラリが提供するAPIがDOM要素を直接参照する場合、それをコンポーネントをまたいで受け渡す必要がある。

計測やアニメーション

要素の大きさを計測するときや、アニメーションライブラリが要素に直接アクセスするとき。

このようなケースで、forwardRef を使うとDOM要素へのアクセスを親コンポーネントまで「引き上げ」てくれる役割を果たします。 反対に、これがないと子コンポーネント内に閉じ込められてしまい、親から操作するのが難しくなるわけです。

forwardRefの基本的な使い方

では具体的に、forwardRef を使ったコンポーネントの作り方を段階的に解説していきます。 ここでは、入力フィールドを持つコンポーネントを作り、それを親コンポーネントからフォーカスさせる例を見てみましょう。

子コンポーネントの定義

まずは子コンポーネントを定義します。 関数コンポーネントの場合は、下記のように forwardRef を使って関数をラップし、第二引数として ref を受け取れるようにします。

import React, { forwardRef } from "react";

function InputField(props, ref) {
  return (
    <input
      type="text"
      placeholder="テキストを入力"
      ref={ref}
      {...props}
    />
  );
}

const ForwardedInputField = forwardRef(InputField);

export default ForwardedInputField;

ここでポイントとなるのが、function InputField(props, ref) の第二引数 です。 通常の関数コンポーネントでは、(props) のみしか使えませんが、forwardRef を利用するとこのように書けます。

親コンポーネントでの利用

次に、親コンポーネントで子コンポーネントを使うときのコードです。 useRef で生成した ref を、先ほど定義した ForwardedInputField に渡すだけで、内部の <input> 要素にアクセスできるようになります。

import React, { useRef } from "react";
import ForwardedInputField from "./ForwardedInputField";

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

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <ForwardedInputField ref={inputRef} />
      <button onClick={handleFocus}>フォーカスを当てる</button>
    </div>
  );
}

export default ParentComponent;

上記のように ref={inputRef} と指定するだけで、子コンポーネント内のDOM要素に対して inputRef.current.focus() のような操作ができるというわけです。 これが forwardRef の基本的な使い方になります。

実務で使える具体例

ここからは、forwardRef をより実務的なシーンで活用する際のヒントを紹介します。 単純にフォーカスを当てるだけでなく、UIライブラリのカスタマイズや外部のスクリプトを統合する際にも役立ちます。

外部UIライブラリのラップ

たとえば、外部のUIコンポーネントライブラリで提供されるコンポーネントをさらに拡張したいケースがあるかもしれません。 その場合、ライブラリ側では ref の受け渡しに対応しているものの、それを自分たちの実装でラップするときに追加のコードが必要になります。

import React, { forwardRef, useRef } from "react";
import SomeLibraryButton from "some-ui-library";

function WrappedButton(props, ref) {
  return <SomeLibraryButton {...props} innerRef={ref} />;
}

const ForwardedWrappedButton = forwardRef(WrappedButton);

function App() {
  const buttonRef = useRef(null);

  const handleClick = () => {
    if (buttonRef.current) {
      // ライブラリの内部APIを呼ぶ場合など
      console.log("Button instance:", buttonRef.current);
    }
  };

  return (
    <div>
      <ForwardedWrappedButton onClick={handleClick} ref={buttonRef}>
        ラップしたボタン
      </ForwardedWrappedButton>
    </div>
  );
}

export default App;

SomeLibraryButton が内部的に ref を受け取る仕組みを持っているとき、forwardRef を使わないとその ref を親にまで引き上げるのが難しくなります。 このようにラップしてあげることで、外部UIライブラリの詳細を意識せずに、利用者側で自由に操作が可能となるわけです。

スクロール位置の制御やアニメーション

Webアプリの中には、任意の要素にスクロールして移動するUIや、スクロールに合わせたアニメーションを表示する仕組みがあります。 スクロール先の要素を親コンポーネントから指定したい場合、DOM要素の位置情報などを取得する必要が出てきます。

import React, { forwardRef, useRef } from "react";

function Section(props, ref) {
  return <div ref={ref}>{props.children}</div>;
}

const ForwardedSection = forwardRef(Section);

function App() {
  const sectionRef = useRef(null);

  const scrollToSection = () => {
    if (sectionRef.current) {
      sectionRef.current.scrollIntoView({ behavior: "smooth" });
    }
  };

  return (
    <div>
      <ForwardedSection ref={sectionRef}>
        <h2>セクションタイトル</h2>
        <p>ここに本文を入れます。</p>
      </ForwardedSection>

      <button onClick={scrollToSection}>このセクションへ移動</button>
    </div>
  );
}

export default App;

このように、ForwardedSection が内部的に持つ <div> 要素の参照を親側で操れるようになると、スムーズスクロールなどの機能が実装しやすくなります。 アニメーションや要素の位置計測も同じ考え方で応用できます。

フォームバリデーション

バリデーションライブラリによっては、DOM 要素に直接アクセスしてエラー表示の位置を調整したり、入力状態を視覚的に変化させるものがあります。 こうしたライブラリを使っているときにも forwardRef は便利です。 たとえば特定の入力欄を強調表示したい、あるいはログを取るために実際の <input> 要素を監視したいといった状況があるかもしれません。

forwardRef はDOM要素への参照を露出するための仕組みです。 乱用するとコードの見通しが悪くなることもあるので、あくまで必要な場合にピンポイントで使うことを意識するといいでしょう。

forwardRefとuseImperativeHandleの違い

React には useImperativeHandle というフックもあり、forwardRef と組み合わせてさらに柔軟な制御ができるようになっています。 ただし最初からすべてを同時に覚えようとすると混乱するかもしれません。

useImperativeHandle は、子コンポーネントが返す ref オブジェクトのカスタマイズを行うための仕組みです。 DOM 要素をそのまま返すのではなく、特定のメソッドだけを公開したい場合などに活用できます。

import React, { useRef, forwardRef, useImperativeHandle } from "react";

function CustomInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focusInput: () => {
      if (inputRef.current) {
        inputRef.current.focus();
      }
    },
    getValue: () => {
      if (inputRef.current) {
        return inputRef.current.value;
      }
      return "";
    }
  }));

  return <input ref={inputRef} {...props} />;
}

const ForwardedCustomInput = forwardRef(CustomInput);

export default ForwardedCustomInput;

こうすると、親コンポーネントは下記のように focusInput()getValue() といった子コンポーネント独自のメソッドを呼び出せます。

import React, { useRef } from "react";
import ForwardedCustomInput from "./ForwardedCustomInput";

function App() {
  const customInputRef = useRef(null);

  const handleFocus = () => {
    if (customInputRef.current) {
      customInputRef.current.focusInput();
    }
  };

  const handleGetValue = () => {
    if (customInputRef.current) {
      const val = customInputRef.current.getValue();
      alert(`現在の入力値: ${val}`);
    }
  };

  return (
    <div>
      <ForwardedCustomInput ref={customInputRef} />
      <button onClick={handleFocus}>フォーカス</button>
      <button onClick={handleGetValue}>値を取得</button>
    </div>
  );
}

export default App;

forwardRef に加えて useImperativeHandle まで活用すると、コンポーネント間の依存関係やデータフローが複雑になりがちです。 しかし、適切に使い分ければ実務シーンでも役立つ場面が多いと思われます。

forwardRefを使う際の注意点

forwardRef は便利ですが、使い方によってはコードの可読性を下げたり、Reactのデータフローを複雑化させるリスクもあります。 ここでは気をつけたいポイントを見てみましょう。

カプセル化の破壊

Reactはコンポーネントごとのカプセル化を前提に作られています。 内側の実装を外から直接触るようになると、仕様変更に弱いコードになりがちです。 「本当にDOM要素に直接触れる必要があるか」を考えたうえで、どうしても必要なときにだけ forwardRef を使うのが良いでしょう。

コンポーネント構造の依存

forwardRef を使っていると、親コンポーネントが子コンポーネントのDOM構造を「ある程度把握している」状態になりがちです。 内部の変更が親に影響してしまう可能性を考慮しながら設計することが重要です。

外部ライブラリの仕様変更

外部UIライブラリなどをラップする場合に、ライブラリ側のAPI仕様が変わると ref の受け渡し方も変わるかもしれません。 このあたりも、forwardRef の活用にともなうリスクとして認識しておきましょう。

実務で forwardRef を多用する場合は、メンテナンス性に注意が必要です。 コードレビュー時などに「本当に ref が必要なケースなのか」をよく検討するのがおすすめです。

まとめ

Reactの forwardRef は、コンポーネント間で DOM 要素への参照を受け渡すときに役立つ便利な機能です。 通常の関数コンポーネントでは props しか受け取れませんが、forwardRef を利用することで子コンポーネントの内部DOMに外側から直接アクセスできるようになります。

いくつかの具体例からもわかるように、以下のようなシーンで重宝します。

  • フォーカスやスクロール位置の制御
  • 外部UIライブラリをラップして拡張したい場合
  • DOM要素の大きさの測定やアニメーション制御

ただし、何でもかんでも DOM 要素を外部に公開するのは、React のコンポーネント設計理念と相反する部分もあります。 forwardRef を利用するときは、「カプセル化を壊すだけの価値があるケースなのか」をしっかり検討することが大切です。

初心者の皆さんにとっては最初は少し複雑に感じるかもしれません。 それでも、フォームのフォーカスを動的に変更したり、第三者のライブラリを取り込んだりするときに「forwardRef があると便利だな」と思う場面はきっと訪れます。 そのときにこの記事で触れたポイントを思い出して、実装を進めてみてください。

以上が、React の forwardRef の基本的な仕組みと活用方法についての解説でした。 焦らず少しずつ試していくと、コンポーネント設計の幅がぐっと広がるはずです。

Reactをマスターしよう

この記事で学んだReactの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。