Closed6

Reactのrefをpropsとして渡す

hoshitahoshita

扱うこと

  • useRefとは
  • Refの使い道(4つ)
  • なぜ囲わないと親コンポーネントからRefを渡すことができないのか
  • 使用方法
  • 注意点
  • 使用できるメソッドを限定したい場合
  • 落とし穴
hoshitahoshita

https://ja.react.dev/reference/react/forwardRef

公式ドキュメントに書いてある通り、forwardRefで囲ってあげれば良い感じにできる

概要

  • forwardRef は、親コンポーネントに対して DOM ノードを ref として公開できるようにします。
  • forwardRef() を呼び出すことで、コンポーネントが ref を受け取ってそれを子コンポーネントに転送 (forward) できるようになります。
  • 引数:コンポーネントのレンダー関数を受け取る。
  • 返り値:JSXでレンダーできるReactコンポーネントを返す

使用方法

  • コンポーネント定義をforwardRef()でラップする
  • 親コンポーネントに DOM ノードを公開する
  • デフォルトでは、各コンポーネント内の DOM ノードはプライベート
  • 時には親にDOMノードを公開したい時がある
  • たとえばノードにフォーカスを与えることを許可したい場合

注意点

  • コンポーネント内の DOM ノードへの ref を公開することで、後でコンポーネントの内部を変更するのが難しくなることに注意してください。
  • 通常は、ボタンやテキスト入力フィールドなどの再利用可能な低レベルコンポーネントからは DOM ノードの公開を行いますが、アバターやコメントのようなアプリケーションレベルのコンポーネントでは行いません。

使用できるメソッドを限定したい場合

  • useImperativeHandleを使用することで限定することができる
const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

落とし穴

  • ref は、props として表現できない、命令型の動作にのみ使用するべきです。
  • 例えば、ノードへのスクロール、ノードへのフォーカス、アニメーションのトリガ、テキストの選択などです。
  • 何かを props として表現できる場合は、ref を使用すべきではありません。
  • 例えば、Modal コンポーネントから { open, close } のような命令型のハンドルを公開するのではなく、<Modal isOpen={isOpen} /> のように、isOpen を props として受け取る方が良いでしょう。命令型の動作を props として公開する際にはエフェクトが役立ちます。

refを渡すには囲う必要がある理由

関数コンポーネントにはインスタンスがないため、デフォルトでは関数コンポーネントに ref 属性を使用することはできません。
https://ja.legacy.reactjs.org/docs/refs-and-the-dom.html#refs-and-function-components

hoshitahoshita

before

type ButtonProps = {
  onClick: () => void;
  ref: (instance: HTMLButtonElement | null) => void;
};


const ShowAllButton = ({
  ref,
  onClick: handleClick
}: ShowAllButtonProps) => {
  return (
    <Box>
      <Button
        ref={ref}
        endIcon={<RightIcon sx={{ color: 'primary.main' }} />}
        onClick={handleClick}
      >
        <Typography variant="button">表示</Typography>
      </Button>
    </Box>
  );
};

refをpropsの一部として渡そうしたが、そのままでは渡すことができずundefinedになる

after

type ButtonProps = {
  onClick: () => void;
};
const ShowAllButton = forwardRef(function ShowAllButton(
  { onClick: handleClick }: ButtonProps,
  ref: Ref<HTMLButtonElement>
) {
  return (
    <Box>
      <Button
        ref={ref}
        endIcon={
          <RightIcon sx={{ color: 'primary.main' }} />
        }
        onClick={handleClick}
      >
        <Typography variant="button">表示</Typography>
      </Button>
    </Box>
  );
});

forwardRefで囲うことでrefを受け取れるようになる。

hoshitahoshita

useRef

https://ja.react.dev/reference/react/useRef

  • useRefは、レンダー時には不要な値を参照するための React フック
  • →Ref は、出力されるコンポーネントの外見に影響しないデータを保存するのに適している
  • →レンダーを跨いで情報を保存できる(通常の変数は、レンダーごとにリセットされる)
  • ref はただの JavaScript オブジェクトですので、変更されたとしても、それを React が知ることはできない

Refの使用場面

  • 出力されるコンポーネントの外見に影響しないデータを保存する時
  • input要素をフォーカス
  • スクロールして画像を表示
  • 動画の再生・停止

通常の値と何が異なる?

  • ref.current プロパティは書き換えが可能です。つまり state と違いミュータブル (mutable)
  • ref.current プロパティを変更しても、React はコンポーネントを再レンダーしない
hoshitahoshita

なぜ囲わないとダメなのか?

  • forwardRefを使用しないと、親コンポーネントが渡したrefは子コンポーネント全体のインスタンスを指す

  • だが、子コンポーネントの特定のDOM要素にアクセスするには、そのrefが正しいDOM要素を指す必要がある

    • forwardRefは、このrefを適切なDOM要素に「転送(フォワード)」する役割を果たす
  • forwardRefを使うことで、親コンポーネントから渡されたrefが子コンポーネント内の特定のDOM要素を指せる

    • これにより、親コンポーネントが子コンポーネントの内部のDOM要素に直接アクセスし、操作することができるようになる
    • forwardRefを使わない場合、親コンポーネントは子コンポーネント全体に対する参照しか持つことができず、内部の特定のDOM要素にアクセスすることができない。
このスクラップは10日前にクローズされました