ReactのforwardRef:React 19で不要になった背景と移行方法

スポンサーリンク

ReactのforwardRef:React 19で不要になった背景と移行方法

forwardRefとは

forwardRef は親コンポーネントから子コンポーネントの DOM要素に ref を渡すためのAPIです。React 18以前では ref が特殊なpropとして扱われていたため、このラッパーが必要でした。React 19からは ref が通常のpropになり、forwardRef は不要になりました。


なぜforwardRefが必要だったか

React 18以前、refkey は特殊なpropとして扱われており、コンポーネントのpropsとして受け取れませんでした。

// React 18以前:refは子コンポーネントのpropsに届かない
function Parent() {
  const inputRef = useRef<HTMLInputElement>(null)

  return <Input ref={inputRef} /> // ← refがInputのpropsに届かない
}

function Input(props) {
  // props.ref は undefined になる
  return <input ref={props.ref} />  // 動かない
}

この問題を解決するために forwardRef でコンポーネントをラップし、2つ目の引数として ref を受け取る必要がありました。


React 18以前:forwardRefの使い方

import { forwardRef, useRef } from 'react'

type InputProps = {
  placeholder?: string
}

// forwardRef でラップして ref を受け取る
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  return <input ref={ref} placeholder={props.placeholder} />
})

// 使う側
function Form() {
  const inputRef = useRef<HTMLInputElement>(null)

  const focusInput = () => {
    inputRef.current?.focus()
  }

  return (
    <div>
      <Input ref={inputRef} placeholder="入力してください" />
      <button onClick={focusInput}>フォーカス</button>
    </div>
  )
}

forwardRef<DOM要素の型, propsの型> のように2つのジェネリクスを指定します。


React 19以降:forwardRefが不要

React 19から ref が通常のpropになりました。forwardRef でラップせず、直接 ref をpropsとして受け取れます。

import { useRef } from 'react'

type InputProps = {
  placeholder?: string
  ref?: React.Ref<HTMLInputElement>
}

// forwardRef 不要
function Input({ placeholder, ref }: InputProps) {
  return <input ref={ref} placeholder={placeholder} />
}

// 使う側は変わらない
function Form() {
  const inputRef = useRef<HTMLInputElement>(null)

  const focusInput = () => {
    inputRef.current?.focus()
  }

  return (
    <div>
      <Input ref={inputRef} placeholder="入力してください" />
      <button onClick={focusInput}>フォーカス</button>
    </div>
  )
}

呼び出し側のコードは変わらず、コンポーネント側がシンプルになります。


コードの新旧比較

// ===== React 18以前 =====
import { forwardRef } from 'react'

const Button = forwardRef<HTMLButtonElement, { label: string }>(
  ({ label }, ref) => (
    <button ref={ref}>{label}</button>
  )
)

// ===== React 19以降 =====
import { Ref } from 'react'

function Button({ label, ref }: { label: string; ref?: Ref<HTMLButtonElement> }) {
  return <button ref={ref}>{label}</button>
}

React.ComponentPropsを使うとさらに簡潔

React.ComponentProps を使うと、HTMLElementの属性を全部含めた形で書けます。ref も含まれています。

// input の全属性(value, onChange, disabled, ref など)を引き継ぐ
function Input(props: React.ComponentProps<'input'>) {
  return <input {...props} />
}

// 追加propsがある場合
type MyInputProps = React.ComponentProps<'input'> & {
  label: string
}

function MyInput({ label, ...props }: MyInputProps) {
  return (
    <div>
      <label>{label}</label>
      <input {...props} />
    </div>
  )
}

使っているReactのバージョンを確認する

npm list react
my-app@1.0.0
└── react@19.0.0

バージョンの詳細確認は「Node.jsのバージョン確認方法」の npm list の章も参照してください。


まとめ

React 18以前 React 19以降
refの扱い 特殊なprop(propsに届かない) 通常のprop
子への転送 forwardRef が必要 そのままpropsで受け取れる
TypeScriptの型 forwardRef<DOM型, Props型> Props に Ref<DOM型> を追加
後方互換 forwardRef は引き続き動作する
  • React 19以降のプロジェクトでは forwardRef を使わずシンプルに書ける
  • 既存コードの forwardRef はそのまま動くので、無理に書き換える必要はない
  • 新規コンポーネントは React 19スタイルで書き、段階的に移行するのが現実的

Reactのプロジェクト作成から始める場合は「React + Vite でプロジェクト作成」を参照してください。