第105章:【v19】ref をPropsみたいに渡す
1️⃣ この章のゴール 🎯
この章がおわるころには、こんなことができるようになります👇
forwardRefを使わずに、refを子コンポーネントに渡せる- 親コンポーネントから、子の
<input>などにフォーカスを当てたりできる - 「あ、
refって 普通の props みたいに 渡せるんだ〜」とイメージできる
React 19 では、関数コンポーネントで ref を そのまま props として受け取れる ようになりました。(React)
その結果、昔は必須だった forwardRef は、基本的にもういりません。(callstack.com)
2️⃣ まずは軽く復習:ref ってなんだっけ? 👀
ref は、ざっくりいうと…
「React の外見(=JSXの世界)から、実際の DOM 要素にそっと触るためのリモコン」
でしたね 🕹️
たとえば:
- ページを開いた瞬間、入力欄にカーソルを置きたい
- ボタンを押したら、特定の
<input>にフォーカスしたい <video>を再生/停止したい
こういうときに useRef とセットで使いました。
// 以前やったシンプルな例(親から <input> を触る)
const inputRef = useRef<HTMLInputElement | null>(null);
return (
<div>
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>
フォーカスする ✨
</button>
</div>
);
ここまでは OK 👍 という前提で進みます。
3️⃣ React 18 までの世界:forwardRef が必要だった時代 🧓
昔(React 18まで) は、ref を「親 → 子コンポーネント → その中の <input>」みたいに渡したいとき、forwardRef というちょっとややこしい関数が必要でした。(React)
イメージとしてはこんな感じ:
// 👵 昔の書き方(React 18 までのイメージ)
import { forwardRef, useRef } from "react";
type MyInputProps = {
label: string;
};
const MyInput = forwardRef<HTMLInputElement, MyInputProps>(
({ label }, ref) => {
return (
<label>
{label}
<input ref={ref} />
</label>
);
}
);
export function App() {
const inputRef = useRef<HTMLInputElement | null>(null);
return (
<div>
<MyInput label="お名前" ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>
フォーカス!
</button>
</div>
);
}
forwardRef(...)でコンポーネントを包む必要がある- 型引数も増えて、TypeScript 的にも少し重たい感じ…😵💫
React 19 では、これが ごっそりいらなくなる のが今日の主役です ✨
4️⃣ React 19 の世界:ref はふつうの Props 🎉
React 19 からは、関数コンポーネントの引数に ref をそのまま書いて OK になりました。(React)
公式ドキュメントのイメージはこんな感じです👇(React)
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// どこか別の場所
<MyInput ref={someRef} />;
つまり:
- 親側:
<MyInput ref={inputRef} />と書くのは今までと同じ 💡 - 子側:
function MyInput(props)の パラメータにrefを追加するだけ
という、とても素直な形になります。
5️⃣ 図でイメージしてみよう 🧠✨
ref がどう流れるか、Mermaid でざっくり図解してみます(イメージだけつかめればOKです 🌈)
6️⃣ TypeScript での基本パターン 🧪(まずはコピペでOK)
React 19 + TypeScript では、ref を扱うとき 「全部ひっくるめて props として受け取る」 パターンがラクです。(Stack Overflow)
💡 子コンポーネント:MyInput.tsx
<input>にそのまま渡せるすべての props(value,onChange,ref, など)をまとめて受け取る- そのまま
<input {...props} />に流す
// MyInput.tsx
import type { ComponentPropsWithoutRef } from "react";
type MyInputProps = ComponentPropsWithoutRef<"input"> & {
label?: string;
};
export function MyInput({ label, ...props }: MyInputProps) {
return (
<label style={{ display: "block", marginBottom: "8px" }}>
{label && <span style={{ marginRight: "8px" }}>{label}</span>}
{/* ここで props の中に ref も混ざっている */}
<input {...props} />
</label>
);
}
ここで大事なのは:
ComponentPropsWithoutRef<"input">の中に、refもふくめて<input>に渡せる props 一式 が入っている、ということ 💼MyInputの中では、{ label, ...props }で分けて、残りをinputに流すだけ
型の意味は「いまはなんとなく」で大丈夫です。 次の第106章で、ここをじっくり分解していきます 🧬
7️⃣ 親コンポーネントから ref を渡してフォーカスしてみよう 🔍
今度は親側です。
App.tsx から MyInput に ref を渡して、ボタンでフォーカスしてみましょう。
🧩 App.tsx
// App.tsx
import { useRef } from "react";
import { MyInput } from "./MyInput";
export function App() {
const nameInputRef = useRef<HTMLInputElement | null>(null);
const handleFocusClick = () => {
// ref.current が null じゃなければ focus() を呼ぶ
nameInputRef.current?.focus();
};
return (
<div style={{ padding: "16px" }}>
<h1>会員登録フォームっぽいなにか 💌</h1>
<MyInput
label="お名前"
placeholder="山田 花子"
ref={nameInputRef}
/>
<button type="button" onClick={handleFocusClick}>
「お名前」入力欄にフォーカスする ✨
</button>
</div>
);
}
ポイントはこの2つだけです 🎯
useRef<HTMLInputElement | null>(null)でrefを作る<MyInput ref={nameInputRef} />と渡す
あとは MyInput の中で <input {...props} /> としているので、
ref がちゃんと <input> に届き、nameInputRef.current から触れるようになります 💪
8️⃣ 「昔との違い」をまとめると… 📚
React 19 では、ref 周りがこんな感じでスッキリしました 👇(React)
-
✅
forwardRefなしで OK- 新しく書くコンポーネントなら、基本的に
forwardRefは使わない方向でOK
- 新しく書くコンポーネントなら、基本的に
-
✅
refはchildrenと同じような「特別な props」- 親から渡せば、子の引数 or
ComponentProps経由で使える
- 親から渡せば、子の引数 or
-
✅ TypeScript 的には
ComponentPropsWithoutRef<"input">などを使うとラク
逆に、これだけは覚えておいてね 👇
refは「DOM を直接さわる最終手段」なので、 何でもかんでもrefに頼るのはNG ✋ まずはstateとpropsで表現できないか考えるのが基本です。(Qiita)
9️⃣ ミニ練習問題 📝(手を動かしてみよう)
時間があれば、こんな練習もしてみてください 💃
-
MyInputをもう1つ増やしてみる- 例:メールアドレス用
<MyInput label="メールアドレス" />
- 例:メールアドレス用
-
emailInputRefを作って、- 「メールアドレスにフォーカス」ボタンを作る
-
さらに、
Enterを押したら 次の入力欄にフォーカス するようにしてみるonKeyDownでe.key === "Enter"のとき、次のrefにfocus()
ここまでできたら、「親 → 子 → DOM」への
refの流れはだいぶ掴めてます 🏆
10️⃣ この章のまとめ 🌈
- React 19 から、
refは 普通の props みたいに 子コンポーネントに渡せる - 新規コンポーネントでは、
forwardRefは基本いらない - TypeScript では
ComponentPropsWithoutRef<"input">を使うとrefを含めた「<input>用の全部入り props」を一気に受け取れて便利 refは「DOM への裏口」なので、乱用せず、必要なところだけで ✨
次の 第106章 では、
「
refを Props で渡すとき、型をどう書くか」
を、もう少しガッツリ TypeScript 目線でやっていきます 🧪 おつかれさまでした〜!☕💕