メインコンテンツへスキップ

useImperativeHandle 完全入門【React】refで公開するAPIを制御する

ReactのuseImperativeHandleとは何か、親コンポーネントから子コンポーネントのメソッドを呼び出す方法を初心者向けに解説。forwardRefとの組み合わせ方や具体的な使用例も紹介します。

#react#hooks#useimperativehandle#javascript#frontend

「子コンポーネントのフォームにフォーカスを当てたい」「子コンポーネントのアニメーションを親から制御したい」——そんなときに使うのが 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 公式ドキュメントを見る

PR

関連記事