React Hooks ∋ useImperativeHandle

useRefと同じ様な使用用途です。useRefはコンポーネント内の HTML 要素を触る為のフックでしたが、useImperativeHandle親コンポーネントにコンポーネント内の要素を触る為の手段を提供する為のフックです。React.forwardRefとの違いは2つほどあると思います。

  1. コンポーネント内の要素を親コンポーネントから直接触られない
  2. コンポーネント内の複数の要素(参照)を親コンポーネントは触ることができる

例。このフックは親から参照をもらうためにforwardRefと共に使います。

const Checkbox = forwardRef((_, ref) => {
  const checkboxRef = useRef(null);

  useImperativeHandle(ref, () => ({
    check() {
      if (checkboxRef.current !== null) {
        checkboxRef.current.checked = true;
      }
    }
  }));

  return <input ref={checkboxRef} type="checkbox" />;
});

こうして渡ってきた親コンポーネントからのref.currentに対して、useImperativeHandleハンドラーを生やします。この例ではcheckメソッドを持つオブジェクトを返していますが、これがまさにref.current.checkを実装していることになります。
この中のcheckboxRefは要素の為のuseRefなので普段通りに使えます。

親コンポーネントからref.current.check()が呼ばれるとコンポーネント内のinput[type=checkbox]のチェックフラグをtrueに書き換えます。(React にはもっと優れた方法があるので実際にはこのように実装しないように)

親コンポーネント

親です。親では何度も言っているref.current.check()の実行の仕組みとハンドラーを生やすためのrefforwardRefなコンポーネントに渡す必要があります。

export default () => {
  const ref = React.useRef(null);
  const onClick = React.useCallback(() => {
    ref.current.check();
  }, []);

  return (
    <div className="App">
      <Checkbox ref={ref} />
      <button onClick={onClick}>check!</button>
    </div>
  );
};

forwardRefに渡すuseRefも基本的にnullでフォールバック値を与えます。これは子要素が存在しない場合はref.currentにはnullが入るようにする為です。

check!ボタンを押してinput[type=checkbox]にチェックが入るか確認できたら完了です。

ユースケース

Pulldown 実装

タブ移動をサポートしないといけないようなアプリケーションの時focus要素の操作にuseImperativeHandleは便利かもしれません。これは実装です。

この要素は、Pulldown にフォーカスが入った後サジェストが開き、その後サジェストの最後の要素が抜けるまでその状態を維持し、抜けたら Pulldown を閉じるというような動作が期待されます。

サジェストの要素どれかにフォーカスがあたってるかをuseImperativeHandleを使って親から調べれるようにすると、親要素である Pulldown の開く・閉じるタイミングが簡単に制御できるようになります。また子の参照だけではなく上の方で書いた Checkbox のように DOM を操作することもできるので、例のように子がそういうハンドラーを提供すれば、親から Focus している要素を変更するといった処理も行えます。