Skip to content

【React学習7】useRefについて

Published: at 00:00

ReactのuseRefについて学習した。

Table of contents

Open Table of contents

useRefとは

useRefは、Reactのフックの一つで、レンダリングに影響しない値やDOM要素への参照を保持するための機能である。

主な用途は、

の2つである。

下記のように宣言する。

const ref = useRef(null);
<input ref={ref} />

useRefcurrentプロパティを持つオブジェクトを返す。 上記は初期値をnullに設定している。

ref.currentプロパティは書き換え可能(ミュータブル)。 ※以前勉強したstateは書き換え不可(イミュータブル)。

再レンダリングが発生してもuseRefが保持しているcurrentの値は維持される。

<input ref={ref} />は「input要素へrefを設定して下さい」という指示。 Reactの場合、直接DOMを操作することが少ないが、DOMへのアクセスが必要な場合、document.querySelectorではなくuseRefを使うことが推奨される。

useStateとの違い

useStateは、変更されると再レンダリングされるのに対し、useRefは、値を書き換えても再レンダリングが発生しないため、画面表示とは関係ない値を保持する用途に向いている。

下記はuseStateuseRefの違いをわかりやすくするためのコード。

「useStateを更新」「useRefを更新」「useRefの値を表示」のボタンを用意。 useRefが裏で値を保持できている状態がわかる。

import { useRef, useState } from "react";

function Counter() {
  const refCount = useRef(0)
  const [count, setCount] = useState(0)
  const [displayCount, setDisplayCount] = useState(0)
  return (
    <div>
      <p>useState: {count}</p>
      <p>useRefの表示用: {displayCount}</p>

      {/*useStateを更新*/}
      <button onClick={() => {
        setCount((prev) => prev + 1)
      }} >
        useStateを更新
      </button>

      {/*useRefを更新*/}
      <button onClick={() => {
        refCount.current++
        console.log(`useRef:` + refCount.current)
      }} >
        useRefを更新
      </button>

      {/*useRefの値を表示*/}
      <button onClick={() => {
        setDisplayCount(refCount.current)
      }} >
        useRefの値を表示
      </button>
    </div>
  );
}

export default Counter;

ちなみに、再読み込みするとuseStateuseRefも初期化される。 useRefは永続的に値を保持するのではなく、コンポーネントの寿命の間だけ保持する。 またcurrentを書き換えても再レンダリングは発生しない。

useRefの使い方

フォーカスを当てる

下記はbuttonをクリックしてinputにフォーカスを当てる例。 inputRef.current?.focus();でDOMを取得→フォーカスを当てる→カーソル移動を行っている。

import React, { useRef } from 'react';

function MyComponent() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    inputRef.current?.focus();
  };

  return (
    <div>
      <input id="counter-input" ref={inputRef} />
      <button onClick={handleClick}>Focus</button>
    </div>
  );
}

所定の位置までスクロールする

下記はbuttonをクリックしてref={scrollRef}を設定したdivまでスクロールさせる例。 scrollRef.current?.scrollIntoView();でDOMを取得→スクロールさせる。 behavior: 'smooth'でスムーズにスクロールさせる。

import React, { useRef } from 'react';

function MyComponent() {
  const scrollRef = useRef<HTMLDivElement>(null);

  const handleClick = () => {
    scrollRef.current?.scrollIntoView({
      behavior: 'smooth',
    });
  };

  return (
    <div>
      <button onClick={handleClick}>Scroll</button>
      <div className='section'>
        <h2>Section 1</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
      </div>
      <div className='section' ref={scrollRef}>
        <h2>Section 2</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
      </div>
    </div>
  );
}

読み込み時に入力欄に自動フォーカス

以下は画面読み込み時に<input id="name-input" ref={inputRef} />にフォーカスを当てる例。 複数input要素があるが、ref={inputRef}のある要素が対象。

import { useRef, useEffect } from "react";

function AutoFocusTest() {
  const inputRef = useRef<HTMLInputElement>(null);

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

  return (
    <div>
      <label htmlFor="name-input">
        Name:
        <input id="name-input" ref={inputRef} />
      </label>
      <label htmlFor="age-input">
        Age:
        <input id="age-input" />
      </label>
      <label htmlFor="address-input">
        Address:
        <input id="address-input" />
      </label>
    </div>
  );
}

export default AutoFocusTest;

タスク追加時に、一番下のタスクに移動

以下はタスクを追加した際に、一番下のタスクに移動する例。 フォームでsubmitを行うと、追加されたタスクまで移動する。 ref={i === tasks.length - 1 ? scrollRef : null}で、最後の要素だけにscrollRefを設定している。 tasks を更新すると再レンダリングが行われる。 その後最後の要素にscrollRefが設定される。 依存配列にtasksを指定したuseEffectが実行されるため、新しく追加された要素までスクロールできる。

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

function TaskList() {
  const [tasks, setTasks] = useState([
    { id: 1, text: 'test 1' },
    { id: 2, text: 'test 2' },
    { id: 3, text: 'test 3' },
  ]);
  const [newTask, setNewTask] = useState('');
  const scrollRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    scrollRef.current?.scrollIntoView({
      behavior: 'smooth',
    })
  }, [tasks])

  return (
    <div>
      {tasks.map((item, i) => (
        <div className='section' key={item.id} ref={i === tasks.length - 1 ? scrollRef : null}>
          <h2>{item.id}</h2>
          <p>{item.text}</p>
        </div>
      ))}
      <div className='section'>
        <form onSubmit={(e) => {
          e.preventDefault();
          setTasks((prev) => [...prev, { id: prev.length + 1, text: newTask }]);
          setNewTask('');
        }}>
          <input name='newTask' type='text' value={newTask} onChange={(e) => {
            setNewTask(e.target.value);
          }} />
          <button type='submit'>Send</button>
        </form>
      </div>
    </div>
  );
}

export default TaskList;

まとめ

useRefは、DOM要素への参照や、レンダリングに影響しない値を保持するためのフックである。 useStateは値を更新すると再レンダリングされるが、useRefはcurrentを書き換えても再レンダリングは発生しない。 そのため、画面表示とは関係なく値を保持したい場合や、DOM要素へアクセスしたい場合に利用するのが適している。