私の外部記憶装置

ただの覚え書きです。ちょこちょこ見直して加筆・修正していますが、間違ってるかも😅

【React】ref(useRef、forwardRef)

概要

  • ref は、
    • state と同様に、レンダー間で変数が維持される
    • state と異なり、セットしても再レンダーがトリガされない
  • コンポーネント内の DOM を操作したい場合、useRefforwardRefを使う

注意点

refオブジェクト.currentコンポーネント本体に書いてはダメ。イベントハンドラやエフェクトから、読み書きする
落とし穴 < 使用法 < useRef – React

使い所

書き方

useRef

useRef – React

useRef ① 導入部

const ref = useRef(initialValue)
  • 左辺:useRef が返す、refオブジェクト。以下の特徴を持つ

    • 唯一のプロパティであるcurrent(=refオブジェクト.current)に、指定された初期値が設定される
    • refオブジェクトを DOM ノードの JSX の ref 属性として渡すと、refオブジェクト.currentにDOMノードが設定され、その DOM ノードを操作できるようになる
    • 2 回目以降のレンダーで、useRef は同じオブジェクトを返す
  • 右辺:useRef

    • 引数は、refオブジェクトcurrent プロパティ(=refオブジェクト.current)の初期値として設定する値
    • DOM 操作の場合、引数は null を設定する
    • この引数は 2 回目以降のレンダーでは無視される
書き方例( useRef ① 導入部)
import { useRef } from 'react';

function MyComponent() {
  const intervalRef = useRef(0);
  // DOM 操作の場合、初期値は null
  const inputRef = useRef(null);
  // ...

useRef ②-1 値の読み書き

ref で値を参照する – React

  • refオブジェクト.currentの読み書き
書き方例( useRef ②-1 値の読み書き)
  • 書き込み
function handleStartClick() {
  const intervalId = setInterval(() => {
    // ...
  }, 1000);
  intervalRef.current = intervalId;
}
  • 呼び出し
function handleStopClick() {
  const intervalId = intervalRef.current;
  clearInterval(intervalId);
}

useRef ②-2 DOM 操作

ref で DOM を操作する – React

  • DOM の操作方法
    1. 操作したいDOMノードのJSXのref属性に、refオブジェクトを渡す
    2. refオブジェクト.currentにDOMノードが設定されるので、イベントハンドラやエフェクトに操作を記述
    3. ノードが画面から削除されると、refオブジェクト.currentnullになる
書き方例( useRef ②-2 DOM 操作)

<input> を操作したい場合】
<input> の ref 属性に refオブジェクト を渡す

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

この DOM ノードが生成され、画面に配置されると、refオブジェクト.current にその DOM ノードが設定される(=その DOM ノードを操作できるようになる)。
これで、<input> の DOM ノードにアクセスして、focus() のようなメソッドを呼び出すことができる

  function handleClick() {
    inputRef.current.focus();
  }

forwardRef

forwardRef – React

const SomeComponent = forwardRef(render)

書き方例( forwardRef )

【親コンポーネントForm から、子の MyInput 内の <input> を操作したい場合】
「子コンポーネント」の定義を forwardRef() でラップ。
ref を、公開したい DOM ノードに渡す。

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const { label, ...otherProps } = props;
  return (
    <label>
      {label}
      // 公開したい DOM ノードに ref を渡す
      <input {...otherProps} ref={ref} />
    </label>
  );
});

これで、親の Form コンポーネントが、MyInput によって公開された <input> DOM ノードにアクセスできるようになる。

function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

書き方例(useRef (① + ②-2) + forwardRef)

DOM操作例1:ボタンクリックで入力欄にフォーカス(関数宣言での書き方 + ファイル分け)

//親コンポーネントのファイル
import { useRef } from 'react';
import HogeInput from './HogeInput.js';
import FugaButton from './FugaButton.js';

export default function Form() {
  // DOM 操作の場合、初期値は null
  const piyoRef = useRef(null);

  function handleClick() {
    piyoRef.current.focus();
  }

  return (
    <>
      <HogeInput ref={piyoRef} />
      <FugaButton onClick={handleClick} />
    </>
  );
}
// HogeInput.js
import { forwardRef } from 'react';

export default forwardRef(
  function HogeInput(props, ref) {
    // 公開したい DOM ノードに ref を渡す
    return <input ref={ref} />;
  }
);
// FugaButton.js
export default function FugaButton({ onClick }) {
  return (
    <>
      <button onClick={onClick}>
        ボタン
      </button>
    </>
  );
}

DOM操作例2:ボタンクリックで入力欄にフォーカス(関数式での書き方)

import { forwardRef, useRef } from 'react';

const HogeInput = forwardRef((props, ref) => {
  // 公開したい DOM ノードに ref を渡す
  return <input ref={ref} />;
});

export default function Form() {
  // DOM 操作の場合、初期値は null
  const piyoRef = useRef(null); 

  function handleClick() {
    piyoRef.current.focus();
  }

  return (
    <>
      <HogeInput ref={piyoRef} />
      <button onClick={handleClick}>
        ボタン
      </button>
    </>
  );
}

参照

useRef – React
forwardRef – React
ref で値を参照する – React
ref で DOM を操作する – React