5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Ateam LifeDesignAdvent Calendar 2023

Day 9

2023年中にuseRefについておさらいしておく

Last updated at Posted at 2023-12-08

こんにちは!!株式会社エイチームライフデザインの @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を返します。

SS.png

再レンダーはトリガされない

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>
}

SS.png

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>
}

SS.png

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の宣言的なパラダイムとは少々異なりますね。
とても便利なのですが、利用の際は慎重になる必要があるかと思います。
「本当にこれでいいのかな?」「よりよい方法はないかな?」
というのは常に考えておきたいですね!

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?