ReactのforwardRef:React 19で不要になった背景と移行方法
forwardRefとは
forwardRef は親コンポーネントから子コンポーネントの DOM要素に ref を渡すためのAPIです。React 18以前では ref が特殊なpropとして扱われていたため、このラッパーが必要でした。React 19からは ref が通常のpropになり、forwardRef は不要になりました。
なぜforwardRefが必要だったか
React 18以前、ref と key は特殊な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 でプロジェクト作成」を参照してください。