useRef は、レンダー時には不要な値を参照するための React フックです。

const ref = useRef(initialValue)

リファレンス

useRef(initialValue)

コンポーネントのトップレベルで useRef を呼び出して、ref を宣言します。

import { useRef } from 'react';

function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...

さらに例を見る

引数

  • initialValue: ref オブジェクトの current プロパティの初期値として設定する値です。任意の型の値を指定できます。この引数は 2 回目以降のレンダーでは無視されます。

返り値

useRef は、以下の 1 つのプロパティだけを持つオブジェクトを返します。

  • current: 渡した initialValue が初期値に設定されます。あとから別の値に変更することができます。ref オブジェクトを JSX ノードの ref 属性として React に渡すと、React は current プロパティの値を設定します。

2 回目以降のレンダーでは、useRef は同じオブジェクトを返します。

注意点

  • ref.current プロパティは書き換えが可能です。つまり state と違いミュータブル (mutable) です。ただし、レンダーに利用されるオブジェクト(state の一部など)を保持している場合は、変更すべきではありません。
  • ref.current プロパティを変更しても、React はコンポーネントを再レンダーしません。ref はただの JavaScript オブジェクトですので、変更されたとしても、それを React が知ることはできないのです。
  • 初期化時を除いて、レンダー中に ref.current の値を読み取ったり書き込んだりしないでください。コンポーネントの振る舞いが予測不能になります。
  • Strict Mode では、純粋でない関数を見つけやすくするために、コンポーネント関数が 2 回呼び出されます。これは開発時のみの振る舞いであり、本番には影響しません。各 ref オブジェクトは 2 回生成されますが、そのうちの 1 つは破棄されます。コンポーネント関数が純粋であれば(そうであるべきです)、この振る舞いはロジックに影響しません。

使用法

ref を使用して値を参照する

コンポーネントのトップレベルで useRef を呼び出し、1 つ以上の ref を宣言します。

import { useRef } from 'react';

function Stopwatch() {
const intervalRef = useRef(0);
// ...

useRef は、唯一のプロパティであるcurrentに、指定された初期値が設定された状態の ref オブジェクトを返します。

次回以降のレンダーでも、useRef は同じオブジェクトを返します。このオブジェクトの current プロパティを書き換えることで情報を保存しておき、あとからその値を読み出すことができます。これは state と似ていますが、大きく違う点があります。

それは、ref を変更しても、再レンダーはトリガされないということです。このことから、ref は、出力されるコンポーネントの外見に影響しないデータを保存するのに適しています。例えば、インターバルの ID を保持しておき、あとから利用したい場合、ref に保存することができます。ref 内の値を更新するには、current プロパティを手動で変更します。

function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}

そして、あとから、ref に保存されているインターバル ID を読み出すことができます。これでインターバルをクリアする関数を呼び出し、インターバルを削除できます。

function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}

ref を使用することで、次のことが保証されます。

  • レンダーを跨いで情報を保存できます(通常の変数は、レンダーごとにリセットされます)。
  • 変更しても再レンダーはトリガされません(state 変数は、変更すると再レンダーがトリガされます)。
  • 保存された情報は、コンポーネントのインスタンスごとに固有です(コンポーネントの外側で定義された変数は、コンポーネントのインスタンス間で共有されます)。

ref を変更しても再レンダーはトリガされないため、ref は画面に表示したい情報を保存するのには適していません。そのような場合は、代わりに useState を使用してください。useRef と useState の使い分けに関しては、ref と state の違いを参照してください。

useRef を用いて値を参照する使用例

例 1/2:
クリックカウンタ

このコンポーネントは、ボタンがクリックされた回数を保存するために ref を使用しています。この例では、クリック回数の読み書きはイベントハンドラ内でのみ行われているため、state の代わりに ref を使用して構いません。

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

JSX 内で {ref.current} を表示すると、クリックしても回数の表示は更新されません。これは、ref.current に新しい値を設定しても、再レンダーがトリガされないためです。レンダーに利用したい値は、代わりに state に保存してください。

落とし穴

レンダー中に ref.current の値を読み取ったり書き込んだりしないでください。

React は、コンポーネント本体の関数が純関数のように振る舞うことを期待しています。

  • 入力値(props、state、context)が同じなら、常に同じ JSX を返さなければなりません。
  • 呼び出し順が変わったり、引数を変えて呼び出されたりしても、他の呼び出し結果に影響を与えてはいけません。

レンダー中に ref を読み書きすると、これらに違反してしまいます。

function MyComponent() {
// ...
// 🚩 Don't write a ref during rendering
myRef.current = 123;
// ...
// 🚩 Don't read a ref during rendering
return <h1>{myOtherRef.current}</h1>;
}

代わりに、イベントハンドラやエフェクトから ref を読み書きしてください。

function MyComponent() {
// ...
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ You can read or write refs in event handlers
doSomething(myOtherRef.current);
}
// ...
}

もし、レンダー中に何かを読み出したり書き込んだりしなければならない場合は、代わりに useState を使用してください。

これらのルールを破っていても、コンポーネントは正常に動作し続けるかもしれません。しかし、近いうちに React に追加される新機能の多くは、これらのルールが守られることを前提としています。詳しくはコンポーネントを純粋に保つを参照してください。


ref で DOM を操作する

DOM を操作したい場合、ref を利用することが非常に多いです。React は、DOM へのアクセスを組み込みでサポートしています。

最初に、初期値を null に設定した ref オブジェクト を宣言します。

import { useRef } from 'react';

function MyComponent() {
const inputRef = useRef(null);
// ...

次に、操作したい DOM ノードの JSX の ref 属性に、ref オブジェクトを渡します。

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

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

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

React は、ノードが画面から削除されると current プロパティを null に戻します。

詳しくは、ref を使用して DOM を操作するを参照してください。

useRef を使用して DOM を操作する例

例 1/4:
input 要素をフォーカス

この例では、ボタンをクリックすると input にフォーカスが当たります。

import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

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

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}


ref の値の再生成を防ぐ

React は、初回に渡された ref の値を保存しますが、それ以降のレンダーではその値は無視します。

function Video() {
const playerRef = useRef(new VideoPlayer());
// ...

new VideoPlayer() の結果は初回レンダーでのみ利用されますが、すべてのレンダーで呼び出し自体は発生しています。これは、計算コストの高いオブジェクトを作成している場合に、無駄が多くなります。

これを解決するために、次のように ref を初期化することができます。

function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...

通常、レンダー中に ref.current の値を読み取ったり書き込んだりすることは許されていません。しかし、今回の場合は問題ありません。なぜなら、呼び出し結果は常に同じであり、条件分岐により書き込みは初期化時にのみ実行されるため、コンポーネントの振る舞いが完全に予測可能となるからです。

さらに深く知る

useRef を遅延して初期化する場合に null チェックを回避する

型チェッカを使用しており null チェックを何度も行うのが煩わしい場合は、次のようなパターンを試してみてください。

function Video() {
const playerRef = useRef(null);

function getPlayer() {
if (playerRef.current !== null) {
return playerRef.current;
}
const player = new VideoPlayer();
playerRef.current = player;
return player;
}

// ...

この例では、playerRef 自体は null 許容です。しかし型チェッカに、getPlayer() は null を返す場合がないと判断させられるはずです。そこでイベントハンドラなどで getPlayer() を使用できます。


トラブルシューティング

独自コンポーネントへの ref を取得できない

以下のようにして、独自コンポーネントに ref を渡そうとしている場合、

const inputRef = useRef(null);

return <MyInput ref={inputRef} />;

コンソールにこのようなエラーが表示されるかもしれません。

Console
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

デフォルトでは、独自コンポーネントは、内部の DOM ノードへの ref を公開していません。

これを修正するには、まず、ref を取得したいコンポーネントを探します。

export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}

そして、次のように forwardRef でラップします。

import { forwardRef } from 'react';

const MyInput = forwardRef(({ value, onChange }, ref) => {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
});

export default MyInput;

これで、親コンポーネントから ref を取得できるようになります。

詳しくは、別のコンポーネントの DOM ノードにアクセスするを参照してください。