この記事はなに?
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 を直接操作する手間を省いてくれるライブラリです。
ほとんどの場合、state
と props
を使ってコンポーネントの状態を管理すれば、期待通りの UI がレンダリングされます。
しかし、ごく稀に、React が管理していない DOM 要素に直接アクセスしたい場面が出てきます。そんな時は ref
を使います。
具体的には、以下のようなユースケースがあります。
-
入力フォームの操作: 特定の
input
やtextarea
要素にユーザーのフォーカスを自動で当てたい、値を直接操作したい場合。- 例: ページロード時に検索ボックスに自動でカーソルを合わせる。
-
メディアコントロール:
video
やaudio
要素の再生・一時停止、音量調整などを、カスタムボタンから制御したい場合。- 例: オリジナルの再生/一時停止ボタンで動画を操作する。
- アニメーションやトランジション: DOM 要素のサイズや位置を直接取得して、複雑なアニメーションを実装したい場合。
-
サードパーティ製ライブラリとの連携: React のコンポーネント内で、DOM 要素への直接的な参照を必要とする JavaScript ライブラリ(D3.js や Chart.js など)を使う場合。
- 例: Chart.js でグラフを描画するために、Canvas 要素への参照を渡す。
ref
は強力な機能ですが、乱用は避け、state
と props
を使った宣言的なデータフローを優先することが重要です。
DOM を直接操作することは、コンポーネントの予測可能性を低下させる可能性があります。
React 18までの ref
の扱い方
React 18 までのバージョンでは、標準の HTML 要素(
,
など)には 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 要素への直接アクセスが必要な特定の場面で使われます。しかし、乱用は避け、state
やprops
を使った宣言的なデータフローを優先しましょう。 - React 18 までのバージョンでは、カスタムコンポーネントに
ref
を渡すためにforwardRef
を使う必要がありました。これにより、コンポーネント内部の DOM 要素へ親コンポーネントからアクセスできるようになります。 - React 19 からは、関数コンポーネントで
ref
を通常のprops
として受け取れるようになります。これによりforwardRef
の記述が不要になり、より簡潔にref
を扱えるようになります。
今後新しい React プロジェクトを始める場合は、React 19 の ref
の新しい扱い方 (ref as a prop) を積極的に利用していくことになると思います。
しかし、既存のコードを読んだり、古いバージョンの React を扱う場合には、forwardRef
の知識が非常に重要になると思います。
この記事が、誰かの ref
と forwardRef
、そして React 19 での変更点の理解に役立てば幸いです。
参考
Views: 0