React useRef完全攻略:コンポーネントの状態管理からタイマー処理まで
Reactにおける useRef
フックは、一見地味に見えますが、実は非常に強力で、多様な場面で役立つユーティリティです。この記事では、useRef
の基本的な使い方から、Reactコンポーネントのライフサイクルを跨いでの値の保持、DOM要素へのアクセス、タイマー処理、さらにはパフォーマンス最適化まで、useRef
を徹底的に解説します。React開発におけるあなたの強力な武器となるように、具体的なコード例を交えながら、useRef
の奥深さを探求していきましょう。
目次
- はじめに:
useRef
とは何か?useRef
の基本的な概念useRef
とuseState
の違いuseRef
の返り値:current
プロパティ
useRef
の基本的な使い方- 初期値の設定
- 値の更新
- 値の参照
- 関数コンポーネント内での
useRef
の利用
useRef
の応用例- DOM要素へのアクセス:
- テキスト入力フィールドへのフォーカス
- 要素の高さや幅の取得
- スクロール位置の制御
- コンポーネントの状態保持:
- レンダー間の値の永続化
- 前の状態の保存と参照
- カウンタの実装
- タイマー処理:
setTimeout
とsetInterval
の利用- コンポーネントのマウント・アンマウント時の処理
- 遅延実行とキャンセルの実装
- DOM要素へのアクセス:
useRef
を使用したパフォーマンス最適化- 不必要な再レンダリングの抑制
- コールバック関数と
useCallback
の連携 - 副作用の制御
useRef
を使う上での注意点current
プロパティの直接的な変更useRef
とステート管理ライブラリ- アンチパターンと避けるべき使い方
useRef
のより高度な使い方- 複数の
useRef
フックの利用 - カスタムフック内での
useRef
の利用 - TypeScript との連携
- 複数の
- まとめ:
useRef
をマスターしてReact開発をレベルアップ
1. はじめに: useRef
とは何か?
Reactの useRef
は、関数コンポーネント内で永続的な値を保持するためのフックです。これは、コンポーネントの再レンダリング間で値を保持し、DOM要素に直接アクセスするためによく使用されます。useRef
を理解することで、Reactコンポーネントの動作をより細かく制御し、様々な問題を効率的に解決できます。
1.1 useRef
の基本的な概念
useRef
は、以下のような特徴を持つフックです。
- 永続性: コンポーネントの再レンダリング間で値を保持します。
- 可変性:
current
プロパティを通じて、値を自由に更新できます。 - 非同期性: 値の変更は再レンダリングをトリガーしません。
これらの特性により、useRef
は主に以下の用途で使用されます。
- DOM要素への直接アクセス
- レンダー間で保持する必要がある可変値の保持 (例: カウンタ、タイマーID)
- 再レンダリングをトリガーせずに値を更新したい場合
1.2 useRef
と useState
の違い
useRef
と useState
は、どちらも関数コンポーネント内で状態を管理するために使用されますが、重要な違いがあります。
特徴 | 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 (
);
}
“`
この例では、inputRef
を input
要素に割り当て、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 (
);
}
“`
この例では、useEffect
フックを使用して、コンポーネントがマウントされた後、elementRef.current.offsetHeight
と elementRef.current.offsetWidth
を使用して、要素の高さと幅を取得しています。
- スクロール位置の制御:
“`javascript
import React, { useRef, useEffect } from ‘react’;
function ScrollToElement() {
const elementRef = useRef(null);
const scrollToElement = () => {
elementRef.current.scrollIntoView({ behavior: ‘smooth’ }); // スムーズスクロール
};
return (
{/ スクロールするためのスペース /}
);
}
“`
この例では、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
を使用して、setTimeout
と setInterval
を安全に管理できます。コンポーネントのマウント・アンマウント時の処理や、遅延実行とキャンセルの実装に役立ちます。
setTimeout
とsetInterval
の利用:
“`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 (
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.current
を false
に設定することで、アンマウント後に実行されるべき処理を安全に制御できます。
- 遅延実行とキャンセルの実装:
“`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
プロパティの直接的な変更:
useRef
の current
プロパティは、直接変更することができます。しかし、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
要素にそれぞれ inputRef1
と inputRef2
を割り当てています。
- カスタムフック内での
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
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アプリケーションを開発しましょう。