6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React 19対応】forwardRefはまだ必要?refの新しい渡し方と基本を解説!

Posted at

この記事はなに?

ref という機能や、forwardRef というコードに遭遇したことはありますか?

これらの機能は、普段のコンポーネント開発ではあまり使わないかもしれませんが、特定の場面で非常に役立つ、あるいは必須となるものです。特に、React 19の登場により ref の扱い方が大きく変わりました。

この記事では、

  • そもそも ref とは何か? なぜ必要なのか?
  • React 18までの ref の渡し方と forwardRef の役割
  • React 19で登場した ref の新しい扱い方
  • そして、どんな時に ref を使うべきなのか?

といった点について、具体的なコード例を交えながら解説したいと思います。

React 19から ref が props に!

React 19から、関数コンポーネントにおいて ref に props としてアクセスできるようになると公式ドキュメントでアナウンスされました。
これは ref の扱いにおいて非常に大きな変更点です。

今までは、カスタムコンポーネントに ref を渡すには forwardRef という特別な関数を使う必要がありました。
しかし、React 19以降の新しい関数コンポーネントでは、この forwardRef が不要になります。

公式ブログによると、将来のバージョンでは forwardRef は非推奨となり、最終的には削除される予定とのことです。

React 19 は2024年12月5日に安定版がリリースされており、今後開発されるアプリケーションではこの新しい ref の渡し方が主流になっていくでしょう。

ref は何に使うの?

React は、UI を宣言的に記述することで、開発者が DOM を直接操作する手間を省いてくれるライブラリです。
ほとんどの場合、stateprops を使ってコンポーネントの状態を管理すれば、期待通りの UI がレンダリングされます。

しかし、ごく稀に、React が管理していない DOM 要素に直接アクセスしたい場面が出てきます。そんな時は ref を使います。

具体的には、以下のようなユースケースがあります。

  • 入力フォームの操作: 特定の inputtextarea 要素にユーザーのフォーカスを自動で当てたい、値を直接操作したい場合。
    • 例: ページロード時に検索ボックスに自動でカーソルを合わせる。
  • メディアコントロール: videoaudio 要素の再生・一時停止、音量調整などを、カスタムボタンから制御したい場合。
    • 例: オリジナルの再生/一時停止ボタンで動画を操作する。
  • アニメーションやトランジション: DOM 要素のサイズや位置を直接取得して、複雑なアニメーションを実装したい場合。
  • サードパーティ製ライブラリとの連携: React のコンポーネント内で、DOM 要素への直接的な参照を必要とする JavaScript ライブラリ(D3.js や Chart.js など)を使う場合。
    • 例: Chart.js でグラフを描画するために、Canvas 要素への参照を渡す。

ref は強力な機能ですが、乱用は避け、stateprops を使った宣言的なデータフローを優先することが重要です。
DOM を直接操作することは、コンポーネントの予測可能性を低下させる可能性があります。

React 18までの ref の扱い方

React 18 までのバージョンでは、標準の HTML 要素(<div>, <input>, <button> など)には ref プロパティを直接渡すことができました。

import React, { useRef, useEffect } from 'react';

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

  useEffect(() => {
    // コンポーネントがマウントされたらinputにフォーカスを当てる
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);

  return (
    <div>
      <label>
        名前:
        <input type="text" ref={inputRef} /> {/* ここにrefを直接渡せる */}
      </label>
      <button onClick={() => inputRef.current.value = ''}>クリア</button>
    </div>
  );
}

export default MyComponent;

しかし、React 18 までのカスタムコンポーネント(自分で作成したコンポーネント)に対しては、ref を直接渡すことはできませんでした。
以下のようなコードを書くと、エラーが発生します。

MyCustomInput.jsx
// ごく普通の関数コンポーネント
function MyCustomInput() {
  return <input type="text" />;
}
ParentComponent.jsx
import React, { useRef, useEffect } from 'react';
import MyCustomInput from './MyCustomInput';

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

  useEffect(() => {
    if (customInputRef.current) {
      // TypeError: customInputRef.current は null または undefined
      // なぜなら、refはMyCustomInputコンポーネント自体には渡らないため
      customInputRef.current.focus();
    }
  }, []);

  return (
    // これではエラーになる!
    <MyCustomInput ref={customInputRef} /> 
  );
}

export default ParentComponent;

なぜなら、ref はデフォルトでは React 要素がレンダリングする実際の DOM 要素に紐づくものであり、カスタムコンポーネントそのものには紐づかないからです。
カスタムコンポーネントは、あくまで複数の DOM 要素や他のコンポーネントを組み合わせた「概念的なもの」です。

この問題を解決するために登場したのが、React.forwardRef でした。

forwardRef の役割

forwardRef は、カスタムコンポーネントの内部にある特定の DOM 要素に、親コンポーネントから ref を渡せるようにするための特別な関数です。親から子へ、ref という「窓口」を開けて、直接連絡できるようにするようなイメージです。

使い方はシンプルで、ref を受け取りたいカスタムコンポーネントを React.forwardRef でラップします。
forwardRef に渡す関数コンポーネントの第2引数として ref が渡されてくるので、それを内部の DOM 要素に渡してあげます。

MyCustomInput.jsx
import React, { forwardRef } from 'react';

// forwardRef でコンポーネントをラップする
const MyCustomInput = forwardRef((props, ref) => {
  return (
    // props から受け取った ref を、内部のinput要素に渡す
    <input type="text" ref={ref} {...props} />
  );
});

export default MyCustomInput;
ParentComponent.jsx
// 使い方は変わらない
import React, { useRef, useEffect } from 'react';
import MyCustomInput from './MyCustomInput';

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

  useEffect(() => {
    // MyCustomInput内のinput要素にアクセスできるようになる
    if (customInputRef.current) {
      customInputRef.current.focus();
    }
  }, []);

  return (
    // MyCustomInputにrefを渡す
    <MyCustomInput ref={customInputRef} placeholder="名前を入力" />
  );
}

export default ParentComponent;

このように forwardRef を使うことで、カスタムコンポーネントの内部にある DOM 要素に、親コンポーネントからアクセスできるようになります。

React 19での ref の新しい扱い方

React 19 では、この forwardRef の記述が不要になり、より直感的な方法で ref をカスタムコンポーネントに渡せるようになります。
それが、ref as a prop (プロップとしての ref) です。

これからは、関数コンポーネントも ref プロップ(他の props と同じように)として受け取ることができるようになります。

React 19 以降の書き方

MyCustomInput.jsx ()
import React from 'react';

// ref を他の props と同じように直接受け取る
function MyCustomInput({ ref, ...props }) {
  return (
    <input type="text" ref={ref} {...props} />
  );
}

export default MyCustomInput;
ParentComponent.jsx
// 使い方は変わらない
import React, { useRef, useEffect } from 'react';
import MyCustomInput from './MyCustomInput';

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

  useEffect(() => {
    if (customInputRef.current) {
      customInputRef.current.focus();
    }
  }, []);

  return (
    // これで MyCustomInput 内の input 要素に ref が渡される
    <MyCustomInput ref={customInputRef} placeholder="名前を入力" />
  );
}

export default ParentComponent;

この変更により、forwardRef の記述が不要になり、コードがより簡潔になります。
既存の forwardRef を使ったコードも、React が提供する自動変換ツール(codemod)で新しい形式に変換できる予定です。

Ref

まとめ

React の ref 機能と、それに関連する forwardRef、そして React 19 での大きな変更点について解説しました。

  • ref は、DOM 要素への直接アクセスが必要な特定の場面で使われます。しかし、乱用は避け、stateprops を使った宣言的なデータフローを優先しましょう。
  • React 18 までのバージョンでは、カスタムコンポーネントに ref を渡すために forwardRef を使う必要がありました。これにより、コンポーネント内部の DOM 要素へ親コンポーネントからアクセスできるようになります。
  • React 19 からは、関数コンポーネントで ref を通常の props として受け取れるようになります。これにより forwardRef の記述が不要になり、より簡潔に ref を扱えるようになります。

今後新しい React プロジェクトを始める場合は、React 19 の ref の新しい扱い方 (ref as a prop) を積極的に利用していくことになると思います。
しかし、既存のコードを読んだり、古いバージョンの React を扱う場合には、forwardRef の知識が非常に重要になると思います。

この記事が、誰かの refforwardRef、そして React 19 での変更点の理解に役立てば幸いです。

参考

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?