React useRef完全攻略:コンポーネントの状態管理からタイマー処理まで

React useRef完全攻略:コンポーネントの状態管理からタイマー処理まで

Reactにおける useRef フックは、一見地味に見えますが、実は非常に強力で、多様な場面で役立つユーティリティです。この記事では、useRef の基本的な使い方から、Reactコンポーネントのライフサイクルを跨いでの値の保持、DOM要素へのアクセス、タイマー処理、さらにはパフォーマンス最適化まで、useRef を徹底的に解説します。React開発におけるあなたの強力な武器となるように、具体的なコード例を交えながら、useRef の奥深さを探求していきましょう。

目次

  1. はじめに: useRef とは何か?
    • useRef の基本的な概念
    • useRefuseState の違い
    • useRef の返り値: current プロパティ
  2. useRef の基本的な使い方
    • 初期値の設定
    • 値の更新
    • 値の参照
    • 関数コンポーネント内での useRef の利用
  3. useRef の応用例
    • DOM要素へのアクセス:
      • テキスト入力フィールドへのフォーカス
      • 要素の高さや幅の取得
      • スクロール位置の制御
    • コンポーネントの状態保持:
      • レンダー間の値の永続化
      • 前の状態の保存と参照
      • カウンタの実装
    • タイマー処理:
      • setTimeoutsetInterval の利用
      • コンポーネントのマウント・アンマウント時の処理
      • 遅延実行とキャンセルの実装
  4. useRef を使用したパフォーマンス最適化
    • 不必要な再レンダリングの抑制
    • コールバック関数と useCallback の連携
    • 副作用の制御
  5. useRef を使う上での注意点
    • current プロパティの直接的な変更
    • useRef とステート管理ライブラリ
    • アンチパターンと避けるべき使い方
  6. useRef のより高度な使い方
    • 複数の useRef フックの利用
    • カスタムフック内での useRef の利用
    • TypeScript との連携
  7. まとめ: useRef をマスターしてReact開発をレベルアップ

1. はじめに: useRef とは何か?

Reactの useRef は、関数コンポーネント内で永続的な値を保持するためのフックです。これは、コンポーネントの再レンダリング間で値を保持し、DOM要素に直接アクセスするためによく使用されます。useRef を理解することで、Reactコンポーネントの動作をより細かく制御し、様々な問題を効率的に解決できます。

1.1 useRef の基本的な概念

useRef は、以下のような特徴を持つフックです。

  • 永続性: コンポーネントの再レンダリング間で値を保持します。
  • 可変性: current プロパティを通じて、値を自由に更新できます。
  • 非同期性: 値の変更は再レンダリングをトリガーしません。

これらの特性により、useRef は主に以下の用途で使用されます。

  • DOM要素への直接アクセス
  • レンダー間で保持する必要がある可変値の保持 (例: カウンタ、タイマーID)
  • 再レンダリングをトリガーせずに値を更新したい場合

1.2 useRefuseState の違い

useRefuseState は、どちらも関数コンポーネント内で状態を管理するために使用されますが、重要な違いがあります。

特徴 useState useRef
目的 コンポーネントの状態管理 可変値の保持、DOM要素へのアクセス
再レンダリング 値の変更時にコンポーネントを再レンダリング 値の変更時にコンポーネントを再レンダリングしない
値の変更 setState 関数を使用 current プロパティを直接変更
主な用途 UIの状態を更新する (例: テキスト入力、チェックボックスの状態) DOM要素へのアクセス、レンダー間で値を保持する

useState は、UIの状態を管理し、変更時にコンポーネントを再レンダリングするために使用されます。一方、useRef は、レンダー間で値を保持し、DOM要素にアクセスするために使用されます。useRef は値を変更してもコンポーネントを再レンダリングしないため、UIに直接影響を与えない値を保持するのに適しています。

1.3 useRef の返り値: current プロパティ

useRef フックは、初期値を引数として受け取り、オブジェクトを返します。このオブジェクトは、current というプロパティを持ちます。current プロパティには、初期値が格納されており、この値を自由に更新できます。

“`javascript
import React, { useRef } from ‘react’;

function MyComponent() {
const myRef = useRef(0); // 初期値は0

console.log(myRef); // { current: 0 }

myRef.current = 10; // 値を10に更新

console.log(myRef); // { current: 10 }

return (

Value: {myRef.current}

);
}
“`

2. useRef の基本的な使い方

useRef の基本的な使い方を、コード例を交えながら解説します。

2.1 初期値の設定

useRef は、初期値を引数として受け取ります。この初期値は、current プロパティの初期値として設定されます。

“`javascript
import React, { useRef } from ‘react’;

function MyComponent() {
const numberRef = useRef(0); // 初期値を0に設定
const stringRef = useRef(‘Hello’); // 初期値を文字列に設定
const objectRef = useRef({}); // 初期値をオブジェクトに設定
const nullRef = useRef(null); // 初期値をnullに設定

return (

Number: {numberRef.current}

String: {stringRef.current}

Object: {JSON.stringify(objectRef.current)}

Null: {nullRef.current ? nullRef.current.toString() : ‘null’}

);
}
“`

2.2 値の更新

useRef の値を更新するには、current プロパティに新しい値を割り当てます。current プロパティの値を変更しても、コンポーネントは再レンダリングされないことに注意してください。

“`javascript
import React, { useRef, useEffect } from ‘react’;

function MyComponent() {
const countRef = useRef(0);

useEffect(() => {
countRef.current = countRef.current + 1;
console.log(‘Ref value:’, countRef.current); // コンソールに更新された値を表示
});

return (

Ref value: {countRef.current}

);
}
“`

この例では、useEffect フックを使用して、コンポーネントがマウントされた後、countRef の値を1ずつインクリメントしています。useEffect は初回レンダー時と、依存配列に指定された変数が変更された場合に実行されます。しかし、countRef の変更は再レンダリングをトリガーしないため、UIは最初のレンダー時の値(0)を表示し続けます。コンソールにはインクリメントされた値が出力されます。

2.3 値の参照

useRef の値を参照するには、current プロパティにアクセスします。

“`javascript
import React, { useRef } from ‘react’;

function MyComponent() {
const inputRef = useRef(null);

const handleClick = () => {
console.log(‘Input value:’, inputRef.current.value); // input要素の値を取得
};

return (


);
}
“`

この例では、inputRefinput 要素に割り当て、handleClick 関数で inputRef.current.value を使用して入力値を取得しています。

2.4 関数コンポーネント内での useRef の利用

useRef は、関数コンポーネント内でのみ利用可能です。クラスコンポーネントでは、React.createRef を使用します。

“`javascript
import React, { useRef } from ‘react’;

function MyComponent() {
const myRef = useRef(null);

return (

);
}
“`

3. useRef の応用例

useRef は、DOM要素へのアクセス、コンポーネントの状態保持、タイマー処理など、様々な場面で役立ちます。それぞれの応用例を具体的なコード例を交えながら解説します。

3.1 DOM要素へのアクセス

useRef を使用して、DOM要素に直接アクセスできます。これは、テキスト入力フィールドへのフォーカス、要素の高さや幅の取得、スクロール位置の制御などに役立ちます。

  • テキスト入力フィールドへのフォーカス:

“`javascript
import React, { useRef, useEffect } from ‘react’;

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

useEffect(() => {
inputRef.current.focus(); // コンポーネントがマウントされたらフォーカス
}, []); // 依存配列を空にすることで、初回レンダー時のみ実行

return (

);
}
“`

この例では、useEffect フックを使用して、コンポーネントがマウントされた後、inputRef.current.focus() を呼び出して、テキスト入力フィールドにフォーカスを当てています。

  • 要素の高さや幅の取得:

“`javascript
import React, { useRef, useEffect } from ‘react’;

function GetElementSize() {
const elementRef = useRef(null);

useEffect(() => {
const height = elementRef.current.offsetHeight;
const width = elementRef.current.offsetWidth;

console.log('Height:', height);
console.log('Width:', width);

}, []);

return (

This is a div element.

);
}
“`

この例では、useEffect フックを使用して、コンポーネントがマウントされた後、elementRef.current.offsetHeightelementRef.current.offsetWidth を使用して、要素の高さと幅を取得しています。

  • スクロール位置の制御:

“`javascript
import React, { useRef, useEffect } from ‘react’;

function ScrollToElement() {
const elementRef = useRef(null);

const scrollToElement = () => {
elementRef.current.scrollIntoView({ behavior: ‘smooth’ }); // スムーズスクロール
};

return (

{/ スクロールするためのスペース /}

This is the element to scroll to.

);
}
“`

この例では、scrollToElement 関数内で、elementRef.current.scrollIntoView({ behavior: 'smooth' }) を呼び出して、要素までスムーズにスクロールしています。

3.2 コンポーネントの状態保持

useRef は、コンポーネントの再レンダリング間で値を保持するために使用できます。これは、レンダー間の値の永続化、前の状態の保存と参照、カウンタの実装などに役立ちます。

  • レンダー間の値の永続化:

“`javascript
import React, { useRef } from ‘react’;

function PersistentValue() {
const renderCountRef = useRef(0);

renderCountRef.current = renderCountRef.current + 1;

return (

Render Count: {renderCountRef.current}

);
}
“`

この例では、renderCountRef を使用して、コンポーネントのレンダー回数をカウントしています。renderCountRef.current の値を更新しても、コンポーネントは再レンダリングされないため、レンダー回数を正確にカウントできます。

  • 前の状態の保存と参照:

“`javascript
import React, { useState, useRef, useEffect } from ‘react’;

function PreviousState() {
const [count, setCount] = useState(0);
const previousCountRef = useRef(0);

useEffect(() => {
previousCountRef.current = count; // countの値をpreviousCountRefに保存
}, [count]);

const increment = () => {
setCount(count + 1);
};

return (

Current Count: {count}

Previous Count: {previousCountRef.current}

);
}
“`

この例では、previousCountRef を使用して、count の前の値を保存しています。useEffect フックを使用して、count の値が変更されたときに、previousCountRef.current の値を更新しています。

  • カウンタの実装:

“`javascript
import React, { useRef, useState, useEffect } from ‘react’;

function Counter() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null); // タイマーIDを保持

useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);

return () => {
  clearInterval(intervalRef.current); // コンポーネントのアンマウント時にクリア
};

}, []); // 初回マウント時のみ実行

const stopCounter = () => {
clearInterval(intervalRef.current);
};

return (

Count: {count}

);
}
“`

この例では、setInterval を使用して、1秒ごとに count の値をインクリメントしています。intervalRef を使用して、setInterval のタイマーIDを保持し、コンポーネントがアンマウントされたときに、clearInterval でタイマーを停止しています。

3.3 タイマー処理

useRef を使用して、setTimeoutsetInterval を安全に管理できます。コンポーネントのマウント・アンマウント時の処理や、遅延実行とキャンセルの実装に役立ちます。

  • setTimeoutsetInterval の利用:

“`javascript
import React, { useRef, useEffect } from ‘react’;

function TimerComponent() {
const timerRef = useRef(null);

useEffect(() => {
timerRef.current = setTimeout(() => {
console.log(‘Timeout executed’);
}, 2000);

return () => {
  clearTimeout(timerRef.current); // コンポーネントのアンマウント時にクリア
};

}, []);

return (

Timer example

);
}
“`

この例では、setTimeout を使用して、2秒後にコンソールにメッセージを出力しています。timerRef を使用して、setTimeout のタイマーIDを保持し、コンポーネントがアンマウントされたときに、clearTimeout でタイマーを停止しています。

  • コンポーネントのマウント・アンマウント時の処理:

“`javascript
import React, { useState, useEffect, useRef } from ‘react’;

function MountingUnmounting() {
const [mounted, setMounted] = useState(true);

const unmountComponent = () => {
setMounted(false);
};

return (

{mounted ? :

Component Unmounted

}

);
}

function MyComponent() {
const isMountedRef = useRef(true);

useEffect(() => {
return () => {
isMountedRef.current = false; // コンポーネントのアンマウント時にfalseに設定
console.log(‘Component unmounted’);
};
}, []);

useEffect(() => {
setTimeout(() => {
if (isMountedRef.current) {
console.log(‘Component is still mounted’);
} else {
console.log(‘Component is already unmounted’);
}
}, 1000);
}, []);

return

MyComponent

;
}
“`

この例では、MyComponent がマウントされているかどうかを isMountedRef で追跡しています。 MyComponent がアンマウントされたときに、isMountedRef.currentfalse に設定することで、アンマウント後に実行されるべき処理を安全に制御できます。

  • 遅延実行とキャンセルの実装:

“`javascript
import React, { useRef, useCallback } from ‘react’;

function DebounceComponent() {
const timerRef = useRef(null);

const delayedFunction = useCallback((value) => {
console.log(‘Delayed function executed with value:’, value);
}, []);

const debounce = (func, delay) => {
return (value) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
func(value);
}, delay);
};
};

const debouncedFunction = useCallback(debounce(delayedFunction, 500), [delayedFunction]);

const handleChange = (e) => {
debouncedFunction(e.target.value);
};

return (

);
}
“`

この例では、debounce 関数を使用して、delayedFunction の実行を遅らせています。timerRef を使用して、setTimeout のタイマーIDを保持し、新しい入力があるたびに、前のタイマーをクリアしています。これにより、入力が完了してから delayedFunction が実行されるようになります。

4. useRef を使用したパフォーマンス最適化

useRef は、不必要な再レンダリングの抑制、コールバック関数と useCallback の連携、副作用の制御など、パフォーマンス最適化にも役立ちます。

  • 不必要な再レンダリングの抑制:

useRef で保持された値を更新しても、コンポーネントは再レンダリングされないため、UIに影響を与えない値を保持するのに適しています。

“`javascript
import React, { useState, useRef } from ‘react’;

function ExpensiveCalculation() {
const [count, setCount] = useState(0);
const calculationRef = useRef(0);

const expensiveCalculation = () => {
// 非常に時間のかかる計算
let result = 0;
for (let i = 0; i < 100000000; i++) {
result += i;
}
calculationRef.current = result;
};

const increment = () => {
expensiveCalculation();
setCount(count + 1);
};

return (

Count: {count}

Calculation Result: {calculationRef.current}

);
}
“`

この例では、expensiveCalculation 関数の実行結果を calculationRef に保存しています。 count の値を変更したときに、expensiveCalculation 関数を実行しますが、計算結果は calculationRef に保存されるため、再レンダリングは発生しません。 これにより、count の変更時に不必要な再レンダリングを抑制し、パフォーマンスを向上させることができます。

  • コールバック関数と useCallback の連携:

“`javascript
import React, { useRef, useCallback } from ‘react’;

function CallbackExample() {
const inputRef = useRef(null);

const handleClick = useCallback(() => {
console.log(‘Input value:’, inputRef.current.value);
}, []); // 依存配列を空にすることで、関数が再生成されない

return (


);
}
“`

この例では、useCallback を使用して、handleClick 関数をメモ化しています。handleClick 関数は、inputRef に依存していますが、inputRef は再レンダリング間で値が変わらないため、useCallback の依存配列を空にすることができます。これにより、handleClick 関数が再レンダリングごとに再生成されるのを防ぎ、パフォーマンスを向上させることができます。

  • 副作用の制御:

useEffect フック内で useRef を使用して、副作用を制御できます。

“`javascript
import React, { useState, useEffect, useRef } from ‘react’;

function EffectControl() {
const [count, setCount] = useState(0);
const prevCountRef = useRef(0);

useEffect(() => {
if (count !== prevCountRef.current) {
// countの値が変更された場合にのみ実行
console.log(‘Count changed to:’, count);
prevCountRef.current = count;
}
}, [count]);

const increment = () => {
setCount(count + 1);
};

return (

Count: {count}

);
}
“`

この例では、prevCountRef を使用して、count の前の値を保持しています。useEffect フック内で、count の値が変更された場合にのみ、副作用を実行するようにしています。これにより、count の値が変更されていない場合に不必要な副作用の実行を抑制し、パフォーマンスを向上させることができます。

5. useRef を使う上での注意点

useRef は便利なフックですが、使い方を間違えると予期せぬ動作を引き起こす可能性があります。useRef を使う上での注意点を解説します。

  • current プロパティの直接的な変更:

useRefcurrent プロパティは、直接変更することができます。しかし、current プロパティの変更は、コンポーネントの再レンダリングをトリガーしません。したがって、current プロパティの変更によってUIを更新する場合は、useState フックを使用する必要があります。

“`javascript
import React, { useState, useRef } from ‘react’;

function IncorrectRefUsage() {
const [count, setCount] = useState(0);
const countRef = useRef(0);

const incrementIncorrectly = () => {
countRef.current = countRef.current + 1;
console.log(‘Ref value:’, countRef.current);
};

const incrementCorrectly = () => {
setCount(count + 1);
};

return (

Count (State): {count}

Count (Ref): {countRef.current}


);
}
“`

この例では、incrementIncorrectly 関数で countRef.current の値をインクリメントしていますが、UIは更新されません。一方、incrementCorrectly 関数で setCount を使用して count の値を更新すると、UIが正しく更新されます。

  • useRef とステート管理ライブラリ:

大規模なアプリケーションでは、Redux、MobX、Context API などのステート管理ライブラリを使用することが一般的です。useRef は、ローカルな状態を管理するのに適していますが、グローバルな状態を管理するには、ステート管理ライブラリを使用する必要があります。

  • アンチパターンと避けるべき使い方:

以下は、useRef のアンチパターンと避けるべき使い方です。

*   **UIの状態を `useRef` で管理する:** UIの状態を管理するには、`useState` フックを使用する必要があります。
*   **副作用を `useRef` で制御する:** 副作用を制御するには、`useEffect` フックを使用する必要があります。
*   **複雑なロジックを `useRef` で実装する:** 複雑なロジックは、カスタムフックに分割して、コードの可読性と保守性を向上させるべきです。

6. useRef のより高度な使い方

useRef は、複数の useRef フックの利用、カスタムフック内での useRef の利用、TypeScript との連携など、より高度な使い方も可能です。

  • 複数の useRef フックの利用:

1つのコンポーネント内で、複数の useRef フックを使用することができます。

“`javascript
import React, { useRef, useEffect } from ‘react’;

function MultipleRefs() {
const inputRef1 = useRef(null);
const inputRef2 = useRef(null);

useEffect(() => {
inputRef1.current.focus(); // 最初のinputにフォーカス
}, []);

const focusSecondInput = () => {
inputRef2.current.focus(); // 2番目のinputにフォーカス
};

return (



);
}
“`

この例では、2つの input 要素にそれぞれ inputRef1inputRef2 を割り当てています。

  • カスタムフック内での useRef の利用:

カスタムフック内で useRef を使用することで、再利用可能なロジックを実装できます。

“`javascript
import { useRef, useEffect } from ‘react’;

function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}

function MyComponent({ value }) {
const previousValue = usePrevious(value);

return (

Current Value: {value}

Previous Value: {previousValue}

);
}
“`

この例では、usePrevious というカスタムフックを定義し、useRef を使用して、前の値を保持しています。

  • TypeScript との連携:

TypeScript で useRef を使用する際には、型アノテーションを使用することで、型安全性を向上させることができます。

“`typescript
import React, { useRef, useEffect } from ‘react’;

interface MyComponentProps {
initialValue: number;
}

function MyComponent({ initialValue }: MyComponentProps) {
const countRef = useRef(initialValue); // 型アノテーションを使用

useEffect(() => {
console.log(‘Count value:’, countRef.current);
}, []);

return (

Count: {countRef.current}

);
}
“`

この例では、useRef<number>(initialValue) と型アノテーションを使用することで、countRef が number 型であることを明示的に指定しています。

7. まとめ: useRef をマスターしてReact開発をレベルアップ

この記事では、Reactの useRef フックについて、基本的な概念から応用例、注意点、高度な使い方まで徹底的に解説しました。useRef は、コンポーネントの状態保持、DOM要素へのアクセス、タイマー処理、パフォーマンス最適化など、様々な場面で役立つ非常に強力なツールです。

useRef をマスターすることで、Reactコンポーネントの動作をより細かく制御し、様々な問題を効率的に解決できます。この記事が、あなたのReact開発における useRef の理解を深め、スキルアップに貢献できることを願っています。

useRef を積極的に活用して、より効率的で高品質なReactアプリケーションを開発しましょう。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール