useRef
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 の違いを参照してください。
例 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 で 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 を操作するを参照してください。
例 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
の値を読み取ったり書き込んだりすることは許されていません。しかし、今回の場合は問題ありません。なぜなら、呼び出し結果は常に同じであり、条件分岐により書き込みは初期化時にのみ実行されるため、コンポーネントの振る舞いが完全に予測可能となるからです。
さらに深く知る
型チェッカを使用しており 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} />;
コンソールにこのようなエラーが表示されるかもしれません。
デフォルトでは、独自コンポーネントは、内部の DOM ノードへの ref を公開していません。
これを修正するには、まず、ref を取得したいコンポーネントを探します。
export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}
次にコンポーネントが受け取る props のリストに ref
を追加し、その ref
を、対応する組み込み子コンポーネントに以下のようにして渡します。
function MyInput({ value, onChange, ref }) {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
};
export default MyInput;
これで、親コンポーネントから ref を取得できるようになります。
詳しくは、別のコンポーネントの DOM ノードにアクセスするを参照してください。