【React】useImperativeHandleを使ってrefへのアクセスを制限する方法

React

ReactでuseImperativeHandleを使ってrefへのアクセスを制限する方法を紹介します。

forwardRefで他のコンポーネントにrefを渡すことが可能になりますが、
DOM操作を通してなんでもできてしまうため、他の開発者に意図していない使われ方をする可能性があります。
そこで使うのが、useImperativeHandleです。
forwardRefに関して知りたい方は、以下を参照してみてください。

useImperativeHandleの使い方

useImperativeHandle(ref, func)というカタチで子コンポーネントで使用します。
第一引数はref、第二引数は使用したいメソッドを含むオブジェクトを返す関数を指定します。

Input.js

import { forwardRef, useImperativeHandle, useRef } from "react";

const Input = forwardRef((props, ref) => {
  // 新しいrefを作成
  const inputRef = useRef();
  // 渡ってきたrefがアクセスできるメソッドを限定する
  useImperativeHandle(ref, () => ({
    customFocus() {
      inputRef.current.focus();
    },
  }));
  // 新しいrefを使用
  return <input type="text" ref={inputRef} />;
});
export default Input;

流れとしては、forwardRef, useImperativeHandle, useRefをインポートします。
そして、新しいrefを作成して、JSXで使用します。
useImperativeHandleの第二引数で渡ってきたrefがアクセスできるメソッドを限定します。
そして親コンポーネントでそのメソッドを実行します。

Sample.js

import { useRef } from "react";
import Input from "./Input";

const Sample = () => {
  const ref = useRef();
  return (
    <>
      <Input ref={ref} />
      <button onClick={() => ref.current.customFocus()}>
        インプット要素にフォーカスを当てる
      </button>
    </>
  );
};

export default Sample;

親コンポーネントで子コンポーネントで作成したメソッドを実行します。

ビデオの再生・停止の使用例

import { useState, useRef, forwardRef, useImperativeHandle } from "react";

const Video = forwardRef(({ path }, ref) => {
  // 新しいrefを作成
  const videoRef = useRef();

  // 使えるメソッドを限定
  useImperativeHandle(ref, () => ({
    customPlay() {
      videoRef.current.play();
    },
    customStop() {
      videoRef.current.pause();
    },
  }));

  return (
    // 新しいrefを使用
    <video style={{ maxWidth: "100%" }} ref={videoRef}>
      <source src={path}></source>
    </video>
  );
});

const Sample = () => {
  const [playing, setPlaying] = useState(false);

  const ref = useRef();

  return (
    <div>
      <Video ref={ref} path="./video.mp4" />
      <button
      // 限定したメソッドを使用
        onClick={() => {
          playing ? ref.current.customStop() : ref.current.customPlay();
          setPlaying((prev) => !prev);
        }}
      >
        {playing ? "ストップ" : "再生"}
      </button>
    </div>
  );
};

export default Sample;

流れは全く同じです。
子コンポーネントで新しいrefを作成し、useImperativeHandleで使えるメソッドを限定します。
そして、JSXでref属性に新しいrefを指定して、親コンポーネントではDOMのメソッドではなく、限定した自作のメソッドを使用します。

基本的にはDOMを直接触ることになるので、使用が増えるとどこで何をしているのか混乱する原因となります。そのため、useStateで解決する場合はuseStateを使うようにしましょう。
一部useRefを使う必要のあるケースがありますので、そのときは、useImperativeHandleを使って実行メソッドを限定しましょう。
useRefを使う必要のあるケースについては、以下を参照してみてください。

React

Posted by devsakaso