React

React 19でforwardRefが不要に|refをpropsで渡す新方式と移行手順

React 19では、関数コンポーネントが ref を通常のpropsとして受け取れるようになり、これまで必須だった forwardRef のラッパーが要らなくなりました。既存の forwardRef は後方互換のためReact 19でも動作しますが、公式は「将来のバージョンで非推奨化・削除する」と明言しています。本記事は、新しい ref as propの書き方、TypeScriptでの型付け、useImperativeHandle、React 19で追加されたrefコールバックのクリーンアップ関数、そしてReact 18コードからの移行手順(codemod)までを、実際のコードで整理します。

まとめ:React 19のref扱いで押さえる5点

  • refがpropになった:関数コンポーネントは function MyInput({ ref }) {} のように ref を直接受け取れる。forwardRef のラップは不要。
  • forwardRefは廃止予定:React 19では動作するが、公式が将来の非推奨化・削除を予告済み。新規コンポーネントでは使わない。
  • useImperativeHandleもforwardRef不要useImperativeHandle(ref, () => ({ ... })) の第1引数にpropの ref をそのまま渡す。
  • TypeScriptはpropsに ref を含めるref: Ref<HTMLInputElement> をprops型へ追加する。
  • 移行はcodemodで自動化npx codemod react/19/remove-forward-ref で既存の forwardRef を一括変換できる。クラスコンポーネントのrefは対象外。

React 19でforwardRefが不要になった理由

React 18以前は、ref がpropsとは別枠の特殊な属性として扱われていました。親から子のDOMや公開メソッドへ参照を渡すには、コンポーネントを forwardRef でラップし、第2引数で受け取った ref を内部要素へ結線する必要がありました。ラッパーが1段挟まるぶん、コンポーネントの型定義やDevToolsの表示が一段複雑になるのが実務上の負担でした。

React 19は ref を関数コンポーネントの通常のpropsに格上げしました。これにより forwardRef の役割そのものが消え、propsの分割代入で ref を取り出してそのまま子要素へ渡せます。公式リリースノートは次の例を挙げています。

React 18以前(forwardRefが必要)

import { forwardRef } from "react";

const MyInput = forwardRef(function MyInput(props, ref) {
  return <input placeholder={props.placeholder} ref={ref} />;
});

React 19以降(refはprop)

function MyInput({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />;
}

// 呼び出し側は変わらない
<MyInput ref={inputRef} placeholder="お名前" />

importから forwardRef が消え、関数の引数に ref が並ぶだけになります。ラッパー関数がなくなるため、React DevToolsのツリーにも ForwardRef(MyInput) ではなく MyInput がそのまま表示され、コンポーネントの追跡が素直になります。なお、この変更は関数コンポーネント限定です。クラスコンポーネントに渡した ref は従来どおりインスタンスを指し、propsとしては渡りません。

refをpropsで受け取る実装とTypeScriptの型付け

ref がpropになったことで、TypeScriptでも「propsの一部」として型を書きます。React 18時代の forwardRef<RefType, PropsType> というジェネリクスは不要になり、props型に ref を1行足すだけです。ref の型は、オブジェクトrefとコールバックrefの両方を受け付ける Ref<T> を使います。

import { useRef } from "react";
import type { Ref } from "react";

type MyInputProps = {
  placeholder: string;
  ref?: Ref<HTMLInputElement>;
};

function MyInput({ placeholder, ref }: MyInputProps) {
  return <input placeholder={placeholder} ref={ref} />;
}

function Form() {
  const inputRef = useRef<HTMLInputElement>(null);
  return <MyInput ref={inputRef} placeholder="お名前" />;
}

useRef<HTMLInputElement>(null) が返す RefObject を、そのまま ref propへ渡せます。親側で .current にアクセスするときは、DOMがマウントされるまで null になり得るため inputRef.current?.focus() のようにオプショナルチェーンで扱います。ref の基礎やuseRefとuseStateの使い分けは、ReactのuseRefの使い方(useStateとの違い・DOM参照)で個別に整理しています。

forwardRef不要で使うuseImperativeHandle

子コンポーネントが公開するメソッド(focus()scrollIntoView() など)を親から呼びたい場合は useImperativeHandle を使います。React 18まではこのフックも forwardRef とセットでしたが、React 19では第1引数にpropの ref をそのまま渡せます。公式リファレンスも forwardRef なしの例に更新されています。

import { useImperativeHandle, useRef } from "react";
import type { Ref } from "react";

type InputHandle = { focus: () => void };

function MyInput({ ref }: { ref?: Ref<InputHandle> }) {
  const inputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current?.focus();
    },
  }), []);

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

公開したいAPIを InputHandle として定義し、ref の型引数に渡すことで、親側でも ref.current?.focus() が型安全に補完されます。内部のDOMを直接露出せず、必要な操作だけを絞って公開できるのは従来と同じで、変わったのは forwardRef のラッパーが消えた点だけです。

refコールバックのクリーンアップ関数(React 19の新機能)

関数形式のref(ref={(node) => { ... }})に、React 19からクリーンアップ関数を返せるようになりました。useEffect と同じ発想で、要素がDOMから外れるときに後片付けを実行できます。サードパーティのチャートやマップなど、DOMノードに紐づくインスタンスの破棄漏れを防げます。

<div
  ref={(node) => {
    const chart = new HeavyChartLibrary(node);
    // NEW: クリーンアップ関数を返すと、要素の除去時に呼ばれる
    return () => {
      chart.destroy();
    };
  }}
/>

公式リリースノートによると、クリーンアップ関数を返した場合、アンマウント時にRef関数が null で呼び出される従来の挙動はスキップされます。クリーンアップはDOM ref・クラスコンポーネントのref・useImperativeHandle のいずれでも機能します。将来的にはアンマウント時に null でrefを呼ぶ挙動自体が非推奨になる予定です。

TypeScriptでは注意が1つあります。ref callbackから値を暗黙に返すと、その戻り値がクリーンアップ関数と解釈されて型エラー(element.ref 周辺)になります。アロー関数で1行に書くと戻り値が生まれてしまうため、副作用だけを行うrefは必ずブロック本体 { ... } にして明示的に何も返さないようにします。

React 18のforwardRefコードをReact 19へ移行する手順

既存プロジェクトの forwardRef は、手作業でも公式codemodでも ref as propへ移行できます。React公式がcodemodを提供しており、codemodレジストリ(react/19/remove-forward-ref)から実行できます。

codemodによる一括変換

npx codemod react/19/remove-forward-ref

このcodemodは forwardRef のimportを外し、ラッパー関数を展開して、propsの分割代入に ref を追加します。実行後は差分をレビューし、型エラーやDevToolsの表示が想定どおりか確認してから取り込みます。コマンド名やオプションはツールの更新で変わり得るため、最新は公式の案内で確認してください。

手動で書き換える場合の要点

  • forwardRef(function C(props, ref) { ... })function C({ ...props, ref }) { ... } へ展開する。
  • importから forwardRef を削除する。
  • TypeScriptは forwardRef<R, P> のジェネリクスを外し、props型に ref: Ref<R> を追加する。
  • useImperativeHandle はそのまま残し、第1引数のrefがpropになった点だけ確認する。

移行の判断基準と互換性

React 19では forwardRef は引き続き動作します。そのため一度に全書き換えする必要はなく、新規コンポーネントから ref as propで書き、既存分はcodemodで段階的に置き換える進め方が現実的です。ただし公式が非推奨化・削除を予告している以上、恒久的に forwardRef へ依存する設計は避けます。クラスコンポーネントに渡すrefはこの変更の対象外で、従来どおりインスタンス参照として扱う点も移行時の確認ポイントです。propsとchildrenを起点にしたコンポーネント設計の考え方は、ReactのPropsとChildrenを活かしたコンポーネント設計もあわせて参考にしてください。

よくある質問

React 19でforwardRefはまだ使えますか?

使えます。React 19では後方互換のため forwardRef は動作します。ただし公式が将来の非推奨化・削除を予告しているため、新規コンポーネントでは ref as propで書くことが推奨されます。

forwardRefはいつ廃止されますか?

具体的な削除バージョンは公表されていません。リリースノートは「将来のバージョンで非推奨化し削除する」とのみ述べています。時期は未定なので、削除前提で早めに移行を進めておくのが安全です。

useImperativeHandleにforwardRefは必要ですか?

React 19では不要です。useImperativeHandle(ref, () => ({ ... })) の第1引数に、propとして受け取った ref を直接渡せます。React 18以前は forwardRef の第2引数からrefを得る必要がありました。

React 19でrefの型はどう書きますか?

props型に ref: Ref<HTMLInputElement> のように追加します。forwardRef<R, P> のジェネリクスは使いません。Ref<T> はオブジェクトrefとコールバックrefの両方に対応します。

React 18と19でrefの何が変わりましたか?

関数コンポーネントで ref がpropsとして扱えるようになった点が最大の変化です。加えて、refコールバックからクリーンアップ関数を返せるようになり、アンマウント時の後片付けを useEffect なしで書けるようになりました。

関連記事

資料請求

RELATED POSTS 関連記事