第108章:【フック】useImperativeHandle(refで「これだけ使っていいよ」と決める)🧙♀️✨
この章は「親が子コンポーネントに対して、必要な操作だけできるようにする」回だよ〜!😊 ポイントは 「refで何でも触らせない」 こと 👍✨
今日のゴール🎯
- 親 → 子に「ref」を渡す
- 子は「useImperativeHandle」で 公開していい機能だけ を返す
- 親は「子の公開機能」だけを呼べるようになる🙌
まずイメージ図🗺️(何が起きてる?)
- 親は「子の中の input要素」そのものを触るんじゃなくて、
- 子が用意した「公開ボタン(API)」だけ押せる感じ!🎮✨
useImperativeHandleってなに?🤔
「refを使って親から操作できるようにする」だけなら、子の中のDOM(inputとか)をそのまま渡しがち💦 でもそれだと親が なんでもできちゃう(=壊しやすい)😇
そこで!
✅ 子が「使っていい操作だけ」をまとめて返すのが「useImperativeHandle」だよ✨ つまり “公開メニューを決めるフック” 🍽️
いつ使うの?(使いどころ)🧩
よくあるのはこのへん👇
- 入力欄にフォーカスを当てたい(focus)⌨️✨
- フォームを一括クリアしたい(clear)🧹
- モーダルを開く/閉じる(open/close)🪟
- 動画を再生/停止(play/pause)🎬
逆に、普段のUI更新は 状態(useState)でやるのが基本 だよ!🧠✨ 「どうしても命令っぽい操作が必要なとき」だけ使うのがコツ👍
実装してみよう!💻✨(v19スタイル:refをPropsみたいに受け取る)
今回は「MyInput」という部品を作って、親から「フォーカスだけ」できるようにするよ😊 (次の第109章にそのまま繋がる!)
フォルダ構成(例)📁
src/
components/
MyInput.tsx
App.tsx
1) 子:MyInput.tsx を作る🧸✨
- 子の中で「本物のinput」を指すために useRef を用意
- useImperativeHandle で「focus関数だけ」公開する
// src/components/MyInput.tsx
import { useImperativeHandle, useRef } from "react";
export type MyInputHandle = {
focus: () => void;
};
type Props = {
label: string;
placeholder?: string;
// React v19: ref を Props の一部みたいに受け取れる
ref?: React.Ref<MyInputHandle>;
};
export function MyInput(props: Props) {
const { label, placeholder, ref } = props;
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(
ref,
() => {
return {
focus: () => {
inputRef.current?.focus();
},
};
},
[]
);
return (
<div style={{ display: "grid", gap: 8 }}>
<label style={{ fontWeight: 700 }}>{label}</label>
<input
ref={inputRef}
placeholder={placeholder}
style={{
padding: "10px 12px",
borderRadius: 10,
border: "1px solid #ccc",
}}
/>
</div>
);
}
ここが超大事ポイント🧠✨
- 親が触れるのは「focus」だけ ✅
- inputの中身(valueとか)は、親が勝手にいじれない 🙅♀️
- 「公開する機能」を子がコントロールできる 💪🌸
2) 親:App.tsx から focus を呼ぶ🎛️✨
親は「MyInputHandle型のref」を作って、ボタンで「focus」を呼ぶよ!
// src/App.tsx
import { useRef } from "react";
import { MyInput, type MyInputHandle } from "./components/MyInput";
export default function App() {
const myInputRef = useRef<MyInputHandle>(null);
return (
<div style={{ padding: 24, display: "grid", gap: 16 }}>
<h1 style={{ margin: 0 }}>useImperativeHandle 練習✨</h1>
<MyInput
ref={myInputRef}
label="ユーザー名"
placeholder="ここに入力してね😊"
/>
<button
onClick={() => myInputRef.current?.focus()}
style={{
padding: "10px 12px",
borderRadius: 10,
border: "none",
cursor: "pointer",
}}
>
入力欄にフォーカスする👉✨
</button>
</div>
);
}
動作チェック✅🎉
- 画面を開く
- ボタンを押す
- 入力欄にカーソルが入ったら成功!🥳✨
ありがちミス集(ハマりどころ)🕳️🧯
1) 押しても何も起きない😢
- 親側:refが「null」のままの可能性 → 「myInputRef.current?.focus()」みたいに「?.」は付けてOK!
2) 子で inputRef.current が null 😭
- inputに「
ref={inputRef}」が付いてるかチェック✅
3) useImperativeHandle の依存配列が気になる🤔
- 今回は「focusだけ」で中身が変わらないから「[]」でOK👍
- もし公開関数が state を使うなら、依存配列を考える必要が出てくるよ🧠✨
ミニ理解図:親が呼べるのは“公開分だけ”🔐
練習問題(やってみよ〜!)📝✨
練習1:clear も公開してみよう🧹
- 「MyInputHandle」に「clear」を追加
- useImperativeHandleで「clear」を実装(inputの中身を空にする)
- App.tsxに「クリア」ボタンを追加
ヒント:inputRef.current があるときに value を空文字にするよ😊
練習2:公開しすぎ禁止ゲーム🚫🎮
わざと「input要素そのもの」を親に渡してみて、 「親が何でもできちゃう危険さ」を体感してみてね😇 (そのあと、ちゃんと useImperativeHandle に戻す!✨)
まとめ💖
- 「useImperativeHandle」は refで操作できる機能を“制限して公開” するフック✨
- 親は「子の中身」を触らず、子が許可した操作だけできる🔐
- 使いどころは「フォーカス」「スクロール」「再生」みたいな命令系が必要なとき👍
次の 第109章 では、今回の「focus公開」をもっときれいに練習して完成させるよ〜!🥰🎉