v19.2Latest

Refによる値の参照

コンポーネントがある情報を「記憶」させたいが、その情報によって新しいレンダーがトリガーされることを望まない場合、refを使用できます。

学習内容
  • コンポーネントにrefを追加する方法
  • refの値を更新する方法
  • refとstateの違い
  • refを安全に使用する方法

コンポーネントへのrefの追加

コンポーネントにrefを追加するには、ReactからuseRefフックをインポートします:

コンポーネント内で、useRefフックを呼び出し、参照したい初期値を唯一の引数として渡します。例えば、値0へのrefは次のとおりです:

useRefは以下のようなオブジェクトを返します:

「ref」と書かれたポケットに、「current」と書かれた矢印が詰め込まれている様子。

イラスト提供:Rachel Lee Nabors

そのrefの現在値には、ref.currentプロパティを通じてアクセスできます。この値は意図的にミュータブルになっており、読み取りと書き込みの両方が可能です。これは、Reactが追跡しないコンポーネントの秘密のポケットのようなものです。(これが、Reactの一方向データフローからの「避難ハッチ」となる理由です。詳細は後述します!)

ここでは、ボタンがクリックされるたびにref.currentをインクリメントします:

このrefは数値を指していますが、stateと同様に、文字列、オブジェクト、さらに関数など、何でも指すことができます。stateと異なり、refは読み書き可能なcurrentプロパティを持つプレーンなJavaScriptオブジェクトです。

注意点として、コンポーネントはインクリメントのたびに再レンダーされません。stateと同様に、refは再レンダー間でReactによって保持されます。しかし、stateを設定するとコンポーネントは再レンダーしますが、refを変更しても再レンダーは起こりません!

例:ストップウォッチの作成

refとstateを1つのコンポーネントで組み合わせることができます。例えば、ユーザーがボタンを押して開始または停止できるストップウォッチを作ってみましょう。「開始」ボタンが押されてから経過した時間を表示するためには、開始ボタンが押された時刻と現在時刻を追跡する必要があります。この情報はレンダリングに使用されるため、stateに保持します:

ユーザーが「開始」を押すと、10ミリ秒ごとに時間を更新するためにsetIntervalを使用します:

「停止」ボタンが押されたときは、既存のインターバルをキャンセルしてnowstate変数の更新を停止する必要があります。これはclearIntervalを呼び出すことで可能ですが、ユーザーが開始を押したときにsetIntervalの呼び出しで以前返されたインターバルIDを渡す必要があります。このインターバルIDをどこかに保持する必要があります。インターバルIDはレンダリングに使用されないため、refに保持できます:

情報がレンダリングに使用される場合は、状態として保持します。情報がイベントハンドラによってのみ必要とされ、変更しても再レンダリングが不要な場合は、refを使用する方が効率的です。

RefとStateの違い

おそらく、refはstateよりも「厳格」ではないように思えるかもしれません。例えば、常に状態設定関数を使用する必要がなく、直接変更できる点などです。しかし、ほとんどの場合、stateを使用すべきです。refは、頻繁には必要としない「緊急避難口」のようなものです。以下にstateとrefの比較を示します:

refstate
useRef(initialValue){ current: initialValue }を返しますuseState(initialValue) は状態変数の現在値と状態設定関数を返します( [value, setValue]
変更しても再レンダリングをトリガーしません。変更すると再レンダリングをトリガーします。
可変 - レンダリングプロセス外でcurrentの値を変更および更新できます。「不変」 - 状態変数を変更して再レンダリングをキューに入れるには、状態設定関数を使用する必要があります。
レンダリング中に currentの値を読み取る(または書き込む)べきではありません。状態はいつでも読み取れます。ただし、各レンダリングには変更されない独自の状態のスナップショットがあります。

以下は、stateを使用して実装されたカウンターボタンです:

count値が表示されるため、それにはstate値を使用するのが理にかなっています。カウンターの値がsetCount()で設定されると、Reactはコンポーネントを再レンダリングし、画面が新しいカウントを反映するように更新されます。

これをrefで実装しようとすると、Reactはコンポーネントを再レンダリングしないため、カウントの変化を確認することはできません!このボタンをクリックしてもテキストが更新されない様子を確認してください:

これが、レンダリング中にref.currentを読み取ると信頼性の低いコードになる理由です。そのような場合は、代わりにstateを使用してください。

Deep Dive
useRefの内部動作

refを使用するタイミング

通常、コンポーネントがReactの外に「踏み出し」、外部API(多くの場合、コンポーネントの外観に影響を与えないブラウザAPI)と通信する必要がある場合にrefを使用します。以下に、そのような珍しい状況のいくつかを示します:

コンポーネントが何らかの値を保存する必要があるが、それがレンダリングロジックに影響を与えない場合は、refを選択してください。

refのベストプラクティス

以下の原則に従うことで、コンポーネントの動作をより予測しやすくなります:

  • refを非常口として扱う。refは、外部システムやブラウザAPIを扱う際に便利です。アプリケーションのロジックやデータフローの多くがrefに依存している場合は、アプローチの見直しを検討してください。
  • レンダリング中にref.currentを読み書きしない。レンダリング中に何らかの情報が必要な場合は、状態を使用してください。Reactはref.currentがいつ変更されるかを知らないため、レンダリング中に読み取るだけでもコンポーネントの動作が予測しにくくなります。(唯一の例外は、if (!ref.current) ref.current = new Thing()のような、初回レンダー時に一度だけrefを設定するコードです。)

React状態の制限はrefには適用されません。例えば、状態は各レンダーのスナップショットのように動作し、同期的には更新されません。しかし、refの現在値を変更すると、即座に変更されます:

これは、ref自体が通常のJavaScriptオブジェクトであるため、そのように動作するからです。

また、refを扱う際には、突然変異の回避について心配する必要もありません。突然変異させるオブジェクトがレンダリングに使用されていない限り、Reactはrefやその内容に対して何を行うかを気にしません。

refとDOM

refは任意の値を指すことができます。しかし、refの最も一般的な使用例はDOM要素へのアクセスです。例えば、プログラムで入力にフォーカスを当てたい場合に便利です。JSXで属性(など)にrefを渡すと、Reactは対応するDOM要素をに配置します。要素がDOMから削除されると、Reactはをに更新します。これについて詳しくは、をお読みください。

まとめ

  • Refは、レンダリングに使用されない値を保持するための緊急避難口です。頻繁に必要になることはありません。
  • Refは、currentという単一のプロパティを持つプレーンなJavaScriptオブジェクトであり、読み取りや設定が可能です。
  • ReactにRefを要求するには、useRefフックを呼び出します。
  • Stateと同様に、Refを使用することでコンポーネントの再レンダリング間で情報を保持できます。
  • Stateとは異なり、Refのcurrent値を設定しても再レンダリングはトリガーされません。
  • レンダリング中にref.currentを読み書きしないでください。これはコンポーネントの予測を困難にします。

チャレンジを試してみる

Challenge 1 of 4:壊れたチャット入力を修正する #

メッセージを入力して「送信」をクリックしてください。「送信しました!」というアラートが表示されるまで3秒の遅延があることに気づくでしょう。この遅延の間に、「取り消し」ボタンが表示されます。それをクリックしてください。この「取り消し」ボタンは、「送信しました!」というメッセージが表示されるのを止めるはずです。これは、clearTimeoutを呼び出し、handleSend中に保存されたタイムアウトIDをクリアすることで実現します。しかし、「取り消し」をクリックした後でも、「送信しました!」メッセージが表示されます。なぜ機能しないのかを探し、修正してください。