こんにちは!!株式会社エイチームライフデザインの @TMDM と申します。
この記事は、Ateam LifeDesign Advent Calendar 2023 シリーズ1の9日目の記事です。
useRefについて理解を深める
useRefには主に2つの用途があります。
- DOM要素への参照
- Reactコンポーネント内の特定のDOM要素に参照できます。参照されたDOMはuseRefで操作が可能です。
- データの保持
- コンポーネントの再レンダリング間で、データを保持することが可能です。
useStateとは違って、値が変わっても再レンダリングされません。
- コンポーネントの再レンダリング間で、データを保持することが可能です。
上記を念頭に置いて、公式の内容を実際に動かして理解していきます。
useRef
レンダー時には不要な値を参照するための React フックです`
- useRefに値を保持させていても、コンポーネントのレンダリングに直接影響をしませんよ!ということ。
使用方法
他のhooksと同じように、トップレベルで呼び出して使用します。
import { useRef } from 'react';
const hoge = () =>{
const inputRef = useRef(null) // 引数に初期値
}
引数
useRef(引数)
← currentプロパティに初期値として設置する値。
2回目以降のレンダーでは無視され、同じオブジェクトを返す。
注意点
- ref.currentはプロパティの書き換えが可能。(stateの一部を持っている場合は変更すべきではない。)
- ref.currentプロパティが変更されても、Reactは再レンダーしません。
- レンダー中にref.currentの値を読んだり書き込んだりはしてはいけない。コンポーネントの振る舞いが予測不可能になる(useState使おう)
refを使用して値を参照する
import { useRef } from 'react';
const hoge = () =>{
const intervalRef = useRef(0);
console.log(intervalRef)
useRefはcurrentしかプロパティを持ちません! : currentの日本語訳はこちら
このcurrentに指定された初期値が、設定された状態のrefを返します。
再レンダーはトリガされない
ref を変更しても、再レンダーはトリガされません。
本当かな?下記コードで確認してみます。
import { FC, useEffect, useRef } from 'react'
export const Test: FC = () => {
const intervalRef = useRef(0)
const handleClick = () => {
intervalRef.current = intervalRef.current + 1
console.log(intervalRef.current)
}
useEffect(() => {
console.log('hello')
})
return <button onClick={handleClick}>クリック</button>
}
helloは1度きりしか出ませんでした。再レンダリングされていませんね。
レンダーを跨いで情報を保存できる
ref は、出力されるコンポーネントの外見に影響しないデータを保存するのに適している。
// useStateと組み合わせてみる。
// useRefの値が増えてもレンダーされない。
import { FC, useEffect, useState, useRef } from 'react'
export const Kv: FC = () => {
const intervalRef = useRef(0)
const [count, setCount] = useState(0)
const handleClick = () => {
setCount((c) => c + 1)
console.log(count)
console.log(intervalRef.current)
}
setInterval(() => {
intervalRef.current = intervalRef.current + 1
}, 500) // 内部でuseRefのカウントだけどんどん進む
useEffect(() => {
console.log('hello')
})
return <button onClick={handleClick}>クリック</button>
}
currentの値は0.5秒ごとにどんどん増えていきますが、helloは出まくりません!!
ref で DOM を操作する
フォーカスボタンを押下すると、inputタグにfocusされる。
import React ,{useRef}from 'react'
const Sample = () => {
const inputRef = useRef(null)
const handleFocus =()=>{
inputRef.current.focus() // inputRefにフォーカスして
}
return (
<div>
<label>
<input type="text" ref={inputRef} />
<button onClick={handleFocus}>フォーカスボタン</button>
</label>
</div>
)
}
export default Sample
refを渡したいのがcomponentだった場合、forwardRefを使用して渡す
子componentの要素をrefを通して操りたい時もありますよね。
親コンポーネントは、直接子のinputにアクセスできませんし、refは通常のpropsとは別に扱われるのでpropsもできません。
そのため、forwardRefを使用して子コンポーネントにrefを渡す必要があります。
import React ,{useRef,forwardRef}from 'react'
const MyInput = forwardRef((props, ref) => { // 必ずこの順序(props, ref)で渡します
return <div>forwardRef<br/><input {...props} ref={ref} /></div>
});
const Sample = () => {
const inputRef = useRef(null)
const handleFocus =()=>{
inputRef.current.focus()
}
return (
<div>
<label>
<MyInput ref={inputRef} />
<button onClick={handleFocus}>フォーカスボタン</button>
</label>
</div>
)
}
export default Sample
forwardRefは玄関みたいですね。
forwardRefは親コンポーネントに対して、内部にアクセスする「入り口」を提供するイメージです。
ref の値の再生成を防ぐ
useRefは初回に渡された ref の値を保存しますが、レンダーのたびに呼び出しは発生してしまう。
const playerRef = useRef(new 重い処理()); // レンダーのたびに無駄が多い...
そのため初期化するのがおすすめ
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new 重い処理(); // 初回レンダーだけ、重い処理をnewする。
}
公式以外にも、useRefを使ったテクニックが紹介されていました。
React×IntersectionObserverで実現する3つの強力なフィーチャー!
- 広告の表示率計測 : コンテンツ作ったけど、ユーザーにどれくらい刺ささっているのか?を計測したいときに良さそうですね。
終わりに
お疲れ様でした!
useRefは命令的な操作ができます。これはReactの宣言的なパラダイムとは少々異なりますね。
とても便利なのですが、利用の際は慎重になる必要があるかと思います。
「本当にこれでいいのかな?」「よりよい方法はないかな?」
というのは常に考えておきたいですね!