useImperativeHandle 完全入門【React】refで公開するAPIを制御する
ReactのuseImperativeHandleとは何か、親コンポーネントから子コンポーネントのメソッドを呼び出す方法を初心者向けに解説。forwardRefとの組み合わせ方や具体的な使用例も紹介します。
「子コンポーネントのフォームにフォーカスを当てたい」「子コンポーネントのアニメーションを親から制御したい」——そんなときに使うのが useImperativeHandle です。
通常のReactでは、データはpropsを通じて親から子へ流れます。しかし、DOMの操作やアニメーションなど命令的な操作が必要な場面では、親から子のメソッドを直接呼び出したいことがあります。useImperativeHandle はこのような場面で役立つフックです。
この記事でわかること:
useImperativeHandleが必要な場面forwardRefとの組み合わせ方- 親から子のメソッドを呼び出す実装パターン
- TypeScript での型定義方法
- 実際のプロダクトでよく使う具体例
- 使いすぎを避けるためのベストプラクティス
useImperativeHandle とは?
useImperativeHandle は、親コンポーネントに公開する ref の内容をカスタマイズするフックです。
通常 ref をコンポーネントに渡すと、親はそのコンポーネントの DOM ノード全体にアクセスできてしまいます。useImperativeHandle を使うと、公開するメソッドを必要なものだけに限定できます。
useImperativeHandle(ref, createHandle関数, 依存配列);| パラメータ | 説明 |
|---|---|
ref | 親から渡された ref オブジェクト |
createHandle | 公開するメソッドを含むオブジェクトを返す関数 |
dependencies | (省略可能)再生成のトリガーとなる値の配列 |
戻り値は undefined です。
基本的な使い方
React 19 以降では ref が通常の props として渡せるようになりました。
import { useRef, useImperativeHandle } from "react";
// 子コンポーネント
function FancyInput({ ref }) {
const inputRef = useRef(null);
// 親に公開するメソッドを定義
useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
clear() {
inputRef.current.value = "";
},
}));
return <input ref={inputRef} type="text" />;
}
// 親コンポーネント
function Form() {
const fancyInputRef = useRef(null);
const handleClick = () => {
fancyInputRef.current.focus(); // 子のfocusメソッドを呼び出す
};
return (
<>
<FancyInput ref={fancyInputRef} />
<button onClick={handleClick}>フォーカス</button>
</>
);
}React 18 以前の書き方(forwardRef)
React 18 以前では forwardRef でコンポーネントをラップする必要があります。
import { useRef, useImperativeHandle, forwardRef } from "react";
// forwardRef でラップする
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView({ behavior: "smooth" });
},
}));
return <input ref={inputRef} type="text" />;
});TypeScript での型定義方法
TypeScript を使う場合、ref の型を明示することで型安全に使えます。
import { useRef, useImperativeHandle, forwardRef } from "react";
// 公開するメソッドの型を定義
type FancyInputHandle = {
focus: () => void;
clear: () => void;
};
// React 19 以降
function FancyInput({ ref }: { ref: React.Ref<FancyInputHandle> }) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus() {
inputRef.current?.focus();
},
clear() {
if (inputRef.current) {
inputRef.current.value = "";
}
},
}));
return <input ref={inputRef} type="text" />;
}
// 親コンポーネントでの使い方
function Form() {
const inputRef = useRef<FancyInputHandle>(null);
const handleSubmit = () => {
// 型補完が効く
inputRef.current?.clear();
};
return (
<>
<FancyInput ref={inputRef} />
<button onClick={handleSubmit}>クリア</button>
</>
);
}実用的な使用例1:動画プレイヤーの制御
import { useRef, useImperativeHandle } from "react";
function VideoPlayer({ src, ref }) {
const videoRef = useRef(null);
// play/pause/seek だけを親に公開する
// DOM の video 要素全体ではなく、必要なメソッドだけ
useImperativeHandle(ref, () => ({
play() {
videoRef.current.play();
},
pause() {
videoRef.current.pause();
},
seekTo(seconds) {
videoRef.current.currentTime = seconds;
},
getCurrentTime() {
return videoRef.current.currentTime;
},
}));
return <video ref={videoRef} src={src} />;
}
function App() {
const playerRef = useRef(null);
return (
<>
<VideoPlayer ref={playerRef} src="/movie.mp4" />
<button onClick={() => playerRef.current.play()}>再生</button>
<button onClick={() => playerRef.current.pause()}>一時停止</button>
<button onClick={() => playerRef.current.seekTo(30)}>30秒へ</button>
</>
);
}実用的な使用例2:アニメーション付きモーダルの制御
モーダルやドロワーの開閉アニメーションを親から命令的に制御する場合です。
import { useRef, useImperativeHandle, useState } from "react";
function AnimatedModal({ ref }) {
const [isVisible, setIsVisible] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
useImperativeHandle(ref, () => ({
open() {
setIsVisible(true);
// アニメーション開始
requestAnimationFrame(() => setIsAnimating(true));
},
close() {
setIsAnimating(false);
// アニメーション終了後に非表示
setTimeout(() => setIsVisible(false), 300);
},
}));
if (!isVisible) return null;
return (
<div
style={{
opacity: isAnimating ? 1 : 0,
transform: isAnimating ? "scale(1)" : "scale(0.9)",
transition: "all 0.3s ease",
position: "fixed",
inset: 0,
backgroundColor: "rgba(0,0,0,0.5)",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div style={{ background: "white", padding: 24, borderRadius: 8 }}>
モーダルの内容
</div>
</div>
);
}
function App() {
const modalRef = useRef(null);
return (
<>
<button onClick={() => modalRef.current.open()}>モーダルを開く</button>
<AnimatedModal ref={modalRef} />
</>
);
}実用的な使用例3:フォームのバリデーションとフォーカス管理
複数フィールドのフォームで、エラー時に最初のエラーフィールドにフォーカスを当てるケースです。
import { useRef, useImperativeHandle, useState } from "react";
function ValidatedInput({ label, validate, ref }) {
const inputRef = useRef(null);
const [error, setError] = useState("");
useImperativeHandle(ref, () => ({
// 外部からバリデーションを実行し、エラー時はフォーカス
validate() {
const value = inputRef.current.value;
const errorMsg = validate(value);
setError(errorMsg || "");
if (errorMsg) {
inputRef.current.focus();
return false;
}
return true;
},
getValue() {
return inputRef.current.value;
},
}));
return (
<div>
<label>{label}</label>
<input ref={inputRef} />
{error && <p style={{ color: "red" }}>{error}</p>}
</div>
);
}
function SignupForm() {
const nameRef = useRef(null);
const emailRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// 上から順にバリデーション、最初のエラーにフォーカスされる
const nameValid = nameRef.current.validate();
const emailValid = emailRef.current.validate();
if (nameValid && emailValid) {
// フォーム送信処理
console.log("送信:", nameRef.current.getValue(), emailRef.current.getValue());
}
};
return (
<form onSubmit={handleSubmit}>
<ValidatedInput
ref={nameRef}
label="氏名"
validate={(v) => (!v ? "氏名は必須です" : "")}
/>
<ValidatedInput
ref={emailRef}
label="メールアドレス"
validate={(v) => (!v.includes("@") ? "正しいメールアドレスを入力してください" : "")}
/>
<button type="submit">送信</button>
</form>
);
}いつ使うべきか?避けるべきか?
useImperativeHandle は命令的な操作にのみ使うのが原則です。
適切な使用例:
- フォームへのフォーカス・バリデーション実行
- スクロール位置の制御
- アニメーションのトリガー・停止
- テキストの選択
- 動画・音声の再生制御
避けるべきケース:
// ❌ NG:propsで表現できるものにrefを使わない
// Modal に open/close メソッドを持たせるより...
ref.current.open();
// ✅ OK:isOpen props で制御する(これが React らしい書き方)
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} />なるべく props でデータを渡して宣言的に書くことがReactの推奨スタイルです。useImperativeHandle は「どうしても命令的に操作しないといけない」場面の最終手段と考えてください。
よくある間違いとアンチパターン
アンチパターン1:状態の読み書きに使う
// ❌ NG:stateの値を外部に公開しない
useImperativeHandle(ref, () => ({
getCount: () => count, // 外部からstateを読むのはNG
setCount: (n) => setCount(n), // 外部からstateを書くのもNG
}));
// ✅ OK:状態の共有はpropsやコンテキストで行うアンチパターン2:すべてのコンポーネントに使う
useImperativeHandle は特殊なケース向けです。通常のコンポーネント間の通信はprops・コールバック・コンテキストで行ってください。
まとめ
useImperativeHandleは親コンポーネントに公開する ref の内容をカスタマイズするフック- DOM ノード全体ではなく、必要なメソッドだけを公開できる
- React 19 以降は
forwardRefなしで使えるが、18 以前はforwardRefが必要 - TypeScript では
useRef<ハンドル型>(null)で型安全に使える - 命令的な操作(フォーカス・スクロール・アニメーション・動画制御)にのみ使う
- props で代替できる場面では使わない
PR
useImperativeHandle 公式ドキュメントで詳細を確認しよう
useImperativeHandleのインタラクティブなサンプルや、forwardRefとの組み合わせ例は公式ドキュメントで確認できます。
useImperativeHandle 公式ドキュメントを見る →