本文書ではReact Hookの中のuseRefの使い方をReact初心者を対象に説明を行っていきます。

useRefの主な利用方法は本文書でも最初に説明を行っているDOMへのアクセスだと思います。Reactのドキュメントでもコード付きで説明されているので非常にシンプルでわかりやすいです。しかしuseRefはuseStateのように変数に値を保持させることも可能です。これについてはドキュメントでもコード入りの説明がないため初心者の人にとってはわかりにくいと思います。本文書ではuseRefでの値の保持の説明についてもシンプルなコードで説明しているのでぜひこの機会にuseRefの使用方法をしっかり理解してください。

input要素のフォーカスに利用

useRefの利用方法することでDOMノード(例:input要素)にアクセスすることが可能です。useRefを使ってinput要素にアクセスすることができるためJavaScriptと同様にfocusメソッドを実行することでinput要素にフォーカスすることが可能です。

idにmyTextFiledを持つiinput要素にフォーカスしたい場合、JavaScriptではdocument.getElementById(‘myTextField’).focus()で行うことが可能です。これと同様なことをReact上で行いたい場合にuseRefを利用することができます。

コンポーネントにinput要素を追加し、useStateを利用してnameを定義します。input要素に文字を入力するとinput要素の下部に入力した文字列が表示されるシンプルなコードです。


import {useState} from 'react'

function App() {
  
  const [name, setName] = useState('')
  const handleOnChange = (e) => setName(e.target.value) 

  return (
    <div style={{'margin':'2em'}}>
      <input type="text" value={name} onChange={handleOnChange} />
      <p>名前:{name}</p>
    </div>
  );
}

export default App;
input要素が表示される
input要素が表示される

ブラウザで確認するとinput要素は表示されますが、表示されているinput要素にはフォーカスはありません。フォーカスを当てるためには自分でカーソルをinput要素に持っていく必要があります。input要素にフォーカスが当たると文字列を入力することができ、入力した文字はinput要素の下に表示されます。

入力した文字がinput要素の下に表示される
入力した文字がinput要素の下に表示される

次にボタンを用意して、ボタンをクリックするとinput要素にフォーカスが当たるようにuseRefを利用します。

input要素を参照するためuseRefを利用してinputElを定義します。useRefの引数には初期値を設定します。ここではnullを設定しています。


import {useState, useRef } from 'react'
//略
const inputEl = useRef(null)

作成したinputElはref属性を使ってinput要素に設定をおこないます。


<input ref={inputEl} type="text" value={name} onChange={handleOnChange} />

ボタンをクリックするとinput要素にフォーカスが当たるようにボタンを追加し、clickイベントを設定します。


<button onClick={handleOnClick}>フォーカスを当てる</button>
ボタンを追加
ボタンを追加

ボタンに設定したクリックイベントのhandelOnClickのメソッドの中でカーソルを当てる処理を設定します。

クリックした時にinputElの中身は何が入っているかconsole.log(inputEl)を利用して確認してみましょう。


const handleOnClick = () => console.log(inputEl)

ボタンをクリックするとコンソールにはオブジェクトが表示されcurrentプロパティにinputが入っていることがわかります。

inputElの中身を確認
inputElの中身を確認

さらにcurrentプロパティの中身を確認してみましょう。


 const handleOnClick = () => console.log(inputEl.current)

currentプロパティにはinput要素が入っていることがわかります。

input要素が表示
input要素が表示

JavaScriptを使って要素に対してfocusメソッドを実行するとその要素に対してフォーカスを当てることができるのでinputEl.currentで取得したinput要素にfocusメソッドを実行します。


 const handleOnClick = () => inputEl.current.focus()

ここまでの設定でボタンをクリックするとinput要素がフォーカスされることが確認できます。

このようにuseRefを利用することでref属性で設定した要素の情報を取得できることがわかりました。またinput要素にref属性を設定した場合はfocusメソッドを利用してフォーカスさせることができることも確認できました。

useRefを利用してinput要素がフォーカスされる
useRefを利用してinput要素がフォーカスされる

また動作確認の中でuseRefはcurrentプロパティに値が保存されることがわかりました。


import {useState, useRef } from 'react'

function App() {

  const inputEl = useRef(null)
  const [name, setName] = useState('')
  const handleOnChange = (e) => setName(e.target.value) 
  const handleOnClick = () => inputEl.current.focus()

  return (
    <div style={{'margin':'2em'}}>
      <input ref={inputEl} type="text" value={name} onChange={handleOnChange} />
      <p>名前:{name}</p>
      <button onClick={handleOnClick}>フォーカスを当てる</button>
    </div>
  );
}

export default App;

input要素にフォーカスすることができましたがDOMにアクセスできるということはアクセスした要素の情報をgetBoundingClientRectメソッドを利用して取得することができます。


const handleOnClick = () => console.log(inputEl.current.getBoundingClientRect())

handleOnClickメソッドの内容を書き換えてボタンをクリックするとコンソールに要素の情報が表示されます。

getBoundingClientRectで取得した値
getBoundingClientRectで取得した値

useRefを使うことで要素にアクセスすることができるのでstyle属性を使って文字の色を変更するといったことも可能になります。


inputEl.current.style.color='red'

useRefに値を保持する

useRefはuseStateと同様に値を保持することができます。useRefは値を保持することが可能ですがuseStateとの違いは値を更新してもコンポーネントの再描写を行いません。

再描写(Re-render)とは何か?

useStateの値を更新すると再描写を行うことがどういうことかまず最初に確認しておきましょう。

useStateでcountを定義し、ボタンをクリックするとcountの値が1増えるコードを記述します。再描写されているか確認するためにconsole.log(‘再描写’)をコードに入れておきます。


import {useState} from 'react'

function App() {
  
  const [count, setCount] = useState(0)
  const handleOnClick = () => setCount(count + 1)

  console.log('再描写');

  return (
    <div style={{'margin':'2em'}}>
      <div>{count}</div>
      <button onClick={handleOnClick}>Countアップ</button>
    </div>
  );
}

export default App;

ブラウザで表示すると下記のように表示されます。”Countアップ”ボタンをクリックして再描写が行われているか確認します。ボタンを押す度に再描写のメッセージがコンソール上に表示され再描写が行われていることが確認できます。

ボタンをクリックする度に再描写が表示
ボタンをクリックする度に再描写が表示

ここまでの動作確認でuseStateでcountの値を更新すると再描写が行われていることがわかりました。

useRefでは再描写しないとは

先ほどの動作確認ではuseStateで定義した変数を更新をすると再描写が行われることが確認できました。ここではuseRefで定義した変数を更新しても再描写が行われないことを確認します。

useRefによって新たにcountRefを定義し、ボタンをクリックするとcountRefの値が更新されるようにします。


import {useState, useRef} from 'react'

function App() {
  
  const [count, setCount] = useState(0)
  const countRef = useRef(0)
  const handleOnClick = () => setCount(count + 1);
  const handleOnClick2 = () => countRef.current++;

  console.log('再描写');

  return (
    <div style={{'margin':'2em'}}>
      <div>{count}</div>
      <button onClick={handleOnClick}>Countアップ</button>
      <div>{countRef.current}</div>
      <button onClick={handleOnClick2}>Count2アップ</button>
    </div>
  );
}

export default App;

useRefを利用して初期値を0にしています。ここでinput要素を利用した時に思い出して欲しいのがuseRefを利用するとcurrentプロパティに値を保持するということです。初期値を0に設定したということはcountRef.currentの値を0に設定したことになります。

更新もcountRef.currentでおこない、表示したい値もcountRef.currentで表示させることができます。

実際にブラウザ上でcount2アップボタンを3回クリックし、countアップボタンを1回クリックします。

どうなると思いますか?

Count2アップボタンを3回押しましたがブラウザ上にもコンソール上にも何も変化はありません。Count2アップボタンのclickイベントに設定したhandleOnClick2が正常に動作していないのではと不安になるかもしれません。

useRefの動作確認
useRefの動作確認

次にCountアップボタンを押してください。Countアップボタンを押すとsetCountメソッドでcountが1更新されコンポーネントの再描写が行われます。そのためコンソールには再描写が表示されます。先ほどまで何回押しても更新されなかったcountRef.currentの値が再描写と同時に更新され、これまでボタンを押した回数が表示されます。

useStateで再描写
useStateで再描写

Count2アップボタンでcountRef.currentが更新されていなかったわけではなくcountRef.currentが更新されていたがコンポーネントの再描写が行われないためブラウザ上に反映されていかなっただけだったのです。

この動作確認からuseRefで定義した変数を更新してもuseStateとは異なりコンポーネントの再描写が行われないということが理解できたかと思います。

useStateとuseRefの違いもわかったと思うのでその違いを理解した上でuseRefを利用する必要があります。