Kodokon kodokon.com

useRef と DOM 操作

レンダーの純粋性を壊さずに、useRef で DOM を操作し、インスタンスの値を保持します。

7 分 · 3 問

このレッスンを Kodokon で開く

useRef は変更可能な箱 - current という単一のプロパティを持つオブジェクト - を返し、その識別子はあらゆるレンダーを生き延びます。state との根本的な違いが 2 つあります。ref.current の変更は 再レンダーを引き起こさず、変更は次のレンダーを待たずに即座に見えます。そこから 2 つの用途の系統が生まれます。DOM ノードを参照すること、そして表示を駆動せずにレンダーをまたいで生き残らなければならないインスタンスの値を格納することです。

JSX
import { useEffect, useRef } from "react";

function SearchField() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} type="search" />;
}
React はレンダーがコミットされた後にノードを ref へ結び付けます。

インスタンスの値の側では、レンダーを引き起こさずに保持しなければならないものはすべて ref に属します。setInterval が返す id、外部ライブラリのインスタンス(地図、エディター、動画プレーヤー)、前回のレンダーの値などです。黄金律は変わりません。レンダーは props と state の純粋関数でなければなりません。ref はエフェクトやイベントハンドラーの中で読み書きするものであり、レンダー中ではありません。

JAVASCRIPT
import { useEffect, useRef } from "react";

function usePrevious(value) {
  const ref = useRef(undefined);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}
エフェクトはレンダーの後に実行されます。ref は 1 レンダー分だけ遅れたままです。

自分のコンポーネントから DOM ノードを公開するには、ref を渡していきます。React 19 以降、ref は関数コンポーネントのごく普通の prop です。それ以前は forwardRef を通すことが必須でした。最後に身につけたい反射がひとつ。結果を記述するのに state で足りるなら(CSS クラス、disabled 属性)、宣言的なアプローチを優先しましょう。ref は React が扱わない領域のために取っておきます。フォーカス、テキスト選択、要素の測定、スクロール、メディア再生などです。

JSX
import { useRef } from "react";

function TextField({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

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

  return (
    <>
      <TextField ref={fieldRef} />
      <button onClick={() => fieldRef.current.focus()}>
        Give focus
      </button>
    </>
  );
}
React 19 以降、ref は他の prop と同じように渡されます。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. 値を格納するうえで useRef と useState を分ける根本的な違いは何ですか?
    • ref.current の変更は、state の更新とは違って再レンダーを引き起こさない
    • useRef はコンポーネントがアンマウントされた後も値を保つ
    • useRef は DOM ノードしか格納できない
    • ref はコンポーネントのすべてのインスタンス間で共有される
  2. なぜレンダー中ではなく useEffect の中で inputRef.current にアクセスするのですか?
    • useEffect が DOM アクセスをまとめてパフォーマンスを高めるから
    • ref はレンダー中は読み取りがロックされているから
    • React はコミットの後にのみノードを結び付けるから。最初のレンダー中は current がまだ null である
  3. usePrevious(value) は一番最初のレンダーで何を返しますか?
    • value の現在の値
    • undefined。ref に書き込むエフェクトがまだ実行されていないから
    • null
    • 次のレンダーの値