ReactのuseRefについて学習した。
Table of contents
Open Table of contents
useRefとは
useRefは、Reactのフックの一つで、レンダリングに影響しない値やDOM要素への参照を保持するための機能である。
主な用途は、
- DOM要素へのアクセス
- 再レンダリングせず値を保持する
の2つである。
下記のように宣言する。
const ref = useRef(null);
<input ref={ref} />
useRefはcurrentプロパティを持つオブジェクトを返す。
上記は初期値をnullに設定している。
ref.currentプロパティは書き換え可能(ミュータブル)。
※以前勉強したstateは書き換え不可(イミュータブル)。
再レンダリングが発生してもuseRefが保持しているcurrentの値は維持される。
<input ref={ref} />は「input要素へrefを設定して下さい」という指示。
Reactの場合、直接DOMを操作することが少ないが、DOMへのアクセスが必要な場合、document.querySelectorではなくuseRefを使うことが推奨される。
useStateとの違い
useStateは、変更されると再レンダリングされるのに対し、useRefは、値を書き換えても再レンダリングが発生しないため、画面表示とは関係ない値を保持する用途に向いている。
下記はuseStateとuseRefの違いをわかりやすくするためのコード。
「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;
ちなみに、再読み込みするとuseStateもuseRefも初期化される。
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要素へアクセスしたい場合に利用するのが適している。