2
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?

【React初心者向け】useRefがイマイチわかんねぇを解決する(useCallback, useMemo)

Last updated at Posted at 2024-08-03

はじめに

useRefってなんとなくはわかるけど、イマイチつかめないという初学者の人は多いはず。
今回はuseRefが力を発揮する状況にフォーカスして解説していきます:writing_hand:

1. DOM要素への直接アクセス

useRefの最も一般的な使用例の1つは、DOM要素に直接アクセスすることです。

import React, { useRef, useEffect } from 'react';

const FocusInput = () => {
  const inputRef = useRef(null);

  useEffect(() => {
    // コンポーネントがマウントされた後に入力フィールドにフォーカスを当てる
    inputRef.current?.focus();
  }, []);

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="This input will be focused" />
    </div>
  );
};

この例では、useRefを使用して入力フィールドへの参照を取得し、コンポーネントのマウント後に自動的にフォーカスを当てています。これは、useStateでは直接実現できない機能です。

2. 以前の値の保持

useRefはレンダリングをトリガーにせず、値を保持するのに適しています。

import React, { useState, useRef, useEffect } from 'react';

const CounterWithPrevious = () => {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  useEffect(() => {
    prevCountRef.current = count;
  }, [count]);

  const prevCount = prevCountRef.current;

  return (
    <div>
      <p>Now: {count}, before: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

この例では、useRefを使って前回のカウント値を保持しています。useStateを使うと再レンダリングが発生してしまうため、useRefを使用するのが適していると言えます。

3. タイマーやインターバルのIDの保持

useRefは、タイマーやインターバルのIDを保持するのに適しています。

import React, { useState, useRef, useEffect } from 'react';

const Timer = () => {
  const [seconds, setSeconds] = useState(0);
  const timerRef = useRef(null);

  const startTimer = () => {
    if (timerRef.current !== null) return; // タイマーが既に動いている場合は何もしない
    timerRef.current = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
  };

  const stopTimer = () => {
    if (timerRef.current === null) return; // タイマーが動いていない場合は何もしない
    clearInterval(timerRef.current);
    timerRef.current = null;
  };

  useEffect(() => {
    // コンポーネントのアンマウント時にタイマーをクリアする
    return () => {
      if (timerRef.current !== null) {
        clearInterval(timerRef.current);
      }
    };
  }, []);

  return (
    <div>
      <p>Seconds: {seconds}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
};

この例では、useRefを使ってタイマーのIDを保持しています。これにより、コンポーネントの再レンダリング間でタイマーのIDを保持し、適切にタイマーを開始・停止できます。

4. 不必要な再レンダリングの回避

値の変更時に再レンダリングをトリガーしたくない場合、useRefが有用です。

import React, { useRef } from 'react';

const HeavyComponent = () => {
  const renderCount = useRef(0);

  renderCount.current += 1;

  return <div>This component has rendered {renderCount.current} times.</div>;
};

この例では、レンダリング回数をカウントしていますが、カウントの更新自体が再レンダリングをトリガーすることはありません。

5. コールバック関数の最新の値へのアクセス

useRefは、useCallbackuseMemoでラップされたコールバック内で最新の値にアクセスする際に役立ちます。

import React, { useState, useRef, useCallback, useEffect } from 'react';

const CallbackExample = () => {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  useEffect(() => {
    countRef.current = count;
  }, [count]);

  const handleClick = useCallback(() => {
    console.log(`Current count: ${countRef.current}`);
  }, []); // 依存配列が空なので、handleClickは一度だけ作成される

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={handleClick}>Log count</button>
    </div>
  );
};

この例では、useRefを使用して最新のcount値を保持し、メモ化されたhandleClick関数内でその値にアクセスしています。

ここでおさらい:writing_hand:

useCallbackとuseMemoの比較:類似点と相違点

ReactのuseCallbackとuseMemoは、両方ともメモ化(memoization)のためのフックですが、それぞれ異なる用途に最適化されています。両者の違いと使用すべき状況について簡単に復習しておきましょう。

1. 基本的な違い

useCallback

  • 目的: 関数のメモ化
  • 返り値: メモ化されたコールバック関数
  • 使用例: イベントハンドラ、子コンポーネントに渡す関数

useMemo

  • 目的: 値のメモ化
  • 返り値: メモ化された値
  • 使用例: 複雑な計算結果、オブジェクトや配列の生成

2. 構文の違い

useCallbackとuseMemoの構文は非常に似ていますが重要な違いがあります。以下で両者を並べて比較し、その違いを詳しく説明します。

基本構文

useCallback

const memoizedCallback = useCallback(callback, dependencies);

useMemo

const memoizedValue = useMemo(() => computation, dependencies);

詳細な比較

特徴 useCallback useMemo
第1引数 コールバック関数 値を返す関数
第2引数 依存配列 依存配列
返り値 メモ化されたコールバック関数 メモ化された値

3. 具体例

useCallback

import React, { useCallback, useState } from 'react';

const ExampleComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []); // 空の依存配列

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
};

この例では:

  • useCallbackは関数全体(() => { ... })を第1引数として受け取ります。
  • 返り値(handleClick)は、メモ化された関数です。

useMemo

import React, { useMemo, useState } from 'react';

const ExampleComponent = () => {
  const [number, setNumber] = useState(0);

  const squaredNumber = useMemo(() => {
    return number * number;
  }, [number]);

  return (
    <div>
      <p>Number: {number}</p>
      <p>Squared: {squaredNumber}</p>
      <button onClick={() => setNumber(prev => prev + 1)}>Increment</button>
    </div>
  );
};

この例では:

  • useMemoは値を計算して返す関数(() => number * number)を第1引数として受け取ります。
  • 返り値(squaredNumber)は、メモ化された値(計算結果)です。

主な違い

  1. 関数 vs 値:

    • useCallbackは関数自体をメモ化します。
    • useMemoは関数の結果(値)をメモ化します。
  2. 返り値の使用:

    • useCallbackの返り値は、そのまま関数として使用できます(例:イベントハンドラとして)。
    • useMemoの返り値は、計算された値なので、直接使用します。
  3. 使用目的:

    • useCallbackは主に子コンポーネントに渡す関数の最適化に使用します。
    • useMemoは複雑な計算や大きなオブジェクトの生成を最適化するのに使用します。

4. いつ使うべきか:stopwatch:

useCallbackを使うべき時

  • 子コンポーネントに関数を渡す場合、特に子コンポーネントがReact.memoでラップされている場合
  • useEffectの依存配列に関数を含める場合
  • パフォーマンスプロファイリングで、関数の再作成が問題になっている場合

useMemoを使うべき時

  • 計算コストが高い処理の結果をメモ化する場合
  • オブジェクトや配列を新しく作成する処理をメモ化する場合
  • レンダリング中に行われる処理を最適化したい場合

5. 注意点

  • 過度の最適化は避けるべきです。これらのフックの使用自体にもコストがあるため、本当に必要な場合にのみ使用しましょう。
  • 依存配列を正しく設定することが重要です。依存配列が適切でないと、期待通りの動作をしない可能性があります。
  • これらのフックは、主にパフォーマンス最適化のためのものです。コードの可読性や保守性を損なわない範囲で使用しましょう。

useCallbackとuseMemoのまとめ:writing_hand:

useCallbackとuseMemoは、どちらもReactアプリケーションのパフォーマンスを最適化するための強力なツールです。useCallbackは関数のメモ化に、useMemoは値のメモ化に使用します。適切に使用することで、不必要な再計算や再レンダリングを防ぎ、アプリケーションのパフォーマンスを向上させることができます。

記事全体まとめ

useRefは以下の状況で使用することを検討しましょう:point_up:

  1. DOM要素への直接アクセスが必要な場合
  2. レンダリングをトリガーせずに値を保持する必要がある場合
  3. タイマーやインターバルのIDを管理する場合
  4. 不必要な再レンダリングを避けたい場合
  5. メモ化されたコールバック内で最新の値にアクセスしたい場合
2
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
2
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?