Reactの基礎 - カスタムHook
概要
カスタムHookは、コンポーネント間でステートフルなロジックを再利用するための仕組みである。
useState、useEffect 等の組み込みHookを組み合わせることにより、複雑な処理をカプセル化して、複数のコンポーネントから呼び出せる独自の関数として定義できる。
カスタムHookの命名規則として、関数名は必ず use で始める必要がある。
(例: useOnlineStatus、useLocalStorage)
この規則は、ESLintのHooksプラグインがカスタムHookを検出するための判定基準でもあり、省略するとHooksのルール違反を検出できなくなるため厳守する必要がある。
カスタムHookの重要な特性として、同じHookを複数のコンポーネントで使用しても、各コンポーネントが保持するステートは独立している。
コンポーネントAとコンポーネントBが同じ useCounter を呼び出しても、それぞれのカウンター値は独立して管理される。
カスタムHookを活用することで、以下に示すメリットが得られる。
| 項目 | 説明 |
|---|---|
| ロジックの再利用 | 同じステートフルなロジックを複数コンポーネントで共有できる。 |
| 関心の分離 | UIとロジックを分離して、コンポーネントをシンプルに保てる。 |
| テスト容易性 | ロジック単体を renderHook でテストできる。
|
| 可読性の向上 | 複雑な処理に意味のある名前を付けてカプセル化できる。 |
カスタムHookの基本
命名規則
ReactのカスタムHook関数の名前は、必ず use プレフィックスで始めるキャメルケース (camelCase) で記述する。
下表に、命名の正誤例を示す。
| 命名例 | 判定 | 説明 |
|---|---|---|
useOnlineStatus |
正しい | use プレフィックスがある。
|
getOnlineStatus |
誤り | use プレフィックスがなく、ESLintが検出できない。
|
useLocalStorage |
正しい | use プレフィックスがある。
|
localStorageHook |
誤り | use プレフィックスがなく、Hooksのルール違反を検出できない。
|
useFetch |
正しい | use プレフィックスがある。
|
ESLintの eslint-plugin-react-hooks プラグインは、use プレフィックスを持つ関数内でのみHooksのルール (条件付き呼び出し禁止等) を検証する。
use プレフィックスを省略すると、この検証が機能しなくなり、バグの検出が困難になる。
基本的な構造
カスタムHookは、useState や useEffect を組み合わせてロジックをカプセル化して、必要な値や関数を返す。
オンライン状態を監視するカスタムHookの例を以下に示す。
import { useState, useEffect } from 'react';
/**
* ユーザのオンライン状態を監視するカスタムHook
* Webブラウザのオンライン / オフラインイベントを監視して、ネットワーク接続状態を返す
*
* @returns 現在のオンライン状態 (true: オンライン, false: オフライン)
*/
function useOnlineStatus(): boolean {
// オンライン状態を管理
// 初期値はnavigator.onLineで現在の接続状態を取得
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
// オンライン / オフラインイベントのリスナーを登録
useEffect(() => {
const handleOnline = () => setIsOnline(true); // オンラインになった時に呼ばれるハンドラ
const handleOffline = () => setIsOnline(false); // オフラインになった時に呼ばれるハンドラ
// イベントリスナーを登録
// onlineイベント : ネットワーク接続が回復した時にイベント発生
window.addEventListener('online', handleOnline);
// offlineイベント : ネットワーク接続が切断された時にイベント発生
window.addEventListener('offline', handleOffline);
// クリーンアップ関数 : コンポーネントのアンマウント時に実行
// イベントリスナーを解除してメモリリークを防止
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []); // 空の依存配列: 初回レンダリング時のみ実行
// 現在のオンライン状態を返す
return isOnline;
}
// 使用例 : ステータスバーでの接続状態表示
function StatusBar() {
// カスタムフックでオンライン状態を取得
const isOnline = useOnlineStatus();
// 接続状態に応じてメッセージを表示
return <p>{isOnline ? 'オンライン' : 'オフライン'}</p>;
}
TypeScriptでの型定義
引数と戻り値の型
TypeScriptでカスタムHookを定義する場合は、引数と戻り値の型を明示することで、使用側での型安全性が向上する。
- カウンタHookの型定義の例
import { useState } from 'react'; // 引数と戻り値の型を明示する function useCounter(initialValue: number): [number, () => void, () => void] { const [count, setCount] = useState<number>(initialValue); const increment = () => setCount((prev) => prev + 1); const decrement = () => setCount((prev) => prev - 1); return [count, increment, decrement]; } // 使用例 function Counter() { const [count, increment, decrement] = useCounter(0); return ( <div> <button onClick={decrement}>-</button> <span>{count}</span> <button onClick={increment}>+</button> </div> ); }
- ジェネリック型を使用することで、型を柔軟に指定できる再利用可能なHookを定義できる。
import { useState } from 'react'; // ジェネリック型を使った汎用的なHook function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] { const [storedValue, setStoredValue] = useState<T>(() => { try { const item = window.localStorage.getItem(key); return item ? (JSON.parse(item) as T) : initialValue; } catch { return initialValue; } }); const setValue = (value: T) => { try { setStoredValue(value); window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.error(error); } }; return [storedValue, setValue]; }
戻り値の設計
カスタムHookの戻り値には、タプル形式とオブジェクト形式の2種類があり、戻り値の数と用途によって使い分ける。
下表に、それぞれの特徴と使い分けの指針を示す。
| 項目 | タプル形式 | オブジェクト形式 |
|---|---|---|
| 戻り値の数 | 2〜3個程度が適切 | 4個以上に対応しやすい。 |
| 命名の自由度 | 分割代入時に任意の名前を付けられる。 | プロパティ名が固定される。 |
| IDE補完 | 型の順序に依存する。 | プロパティ名で補完が効きやすい。 |
| 主な用途 | useState や useReducer と同様の形式 |
戻り値の意味を明確にする場合 |
オブジェクト形式の戻り値の例を以下に示す。
import { useState } from 'react';
interface UseFormInput {
value: string;
setValue: (v: string) => void;
reset: () => void;
isEmpty: boolean;
}
function useFormInput(initialValue: string): UseFormInput {
const [value, setValue] = useState<string>(initialValue);
const reset = () => setValue(initialValue);
const isEmpty = value.trim() === '';
return { value, setValue, reset, isEmpty };
}
// 使用例
function LoginForm() {
const username = useFormInput('');
const password = useFormInput('');
return (
<form>
<input value={username.value} onChange={(e) => username.setValue(e.target.value)} />
<input value={password.value} onChange={(e) => password.setValue(e.target.value)} />
<button onClick={username.reset}>リセット</button>
</form>
);
}
サンプルコード : カスタムHook
useLocalStorage
useLocalStorage は、ローカルストレージとReactのステートを自動的に同期するHookである。
以下の例では、他タブからの変更を検知するために storage イベントを購読している。
import { useState, useEffect } from 'react';
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue;
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
}
catch {
return initialValue;
}
});
const setValue = (value: T) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
}
catch (error) {
console.error(error);
}
};
// 他タブでの変更を検知する
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === key && e.newValue !== null) {
setStoredValue(JSON.parse(e.newValue) as T);
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [key]);
return [storedValue, setValue];
}
useFetch
useFetch は、データフェッチングの状態 (data、loading、error) を管理するHookである。
以下の例では、ignore フラグを使用して、コンポーネントのアンマウント後のステート更新を防止している。
import { useState, useEffect } from 'react';
// データフェッチング結果の型定義
// T: 取得するデータの型
interface UseFetchResult<T> {
data: T | null; // 取得したデータ (成功時)
loading: boolean; // ローディング中フラグ
error: Error | null; // エラーオブジェクト (失敗時)
}
/**
* 指定したURLからデータを非同期で取得するカスタムフック
* データフェッチングの状態 (data, loading, error) を統一的に管理する
*
* @template T - 取得するデータの型
* @param url - データを取得するエンドポイントのURL
* @returns データ、ローディング状態、エラーを含むオブジェクト
*/
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null); // 取得したデータを管理する状態
const [loading, setLoading] = useState<boolean>(true); // ローディング状態を管理 (初期値はtrue:即座にフェッチ開始するため)
const [error, setError] = useState<Error | null>(null); // エラー情報を管理
useEffect(() => {
// アンマウント後のステート更新を防止するためのフラグ
// クリーンアップ関数でtrueに設定される
let ignore = false;
// データを非同期で取得する内部関数
const fetchData = async () => {
setLoading(true); // ローディング開始
setError(null); // エラーをクリア
try {
const response = await fetch(url); // 指定されたURLからデータをフェッチ
// HTTPエラーレスポンスの場合は例外をスロー
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
const json = (await response.json()) as T; // レスポンスボディをJSONとしてパース
// アンマウントされていない場合のみデータをセット
if (!ignore) setData(json);
}
catch (err) {
// エラーハンドリング : アンマウントされていない場合のみエラーをセット
if (!ignore) setError(err instanceof Error ? err : new Error(String(err)));
}
finally {
// 成功 / 失敗に関わらずローディング状態を解除
if (!ignore) setLoading(false);
}
};
// データ取得を実行
fetchData();
// クリーンアップ関数 : アンマウント時にignoreフラグをtrueに設定
// これにより、アンマウント後のステート更新が防止される (メモリリーク対策)
return () => {
ignore = true;
};
}, [url]); // URLが変更されたら再フェッチ
// 結果をオブジェクトとして返す
return { data, loading, error };
}
useDebounce
useDebounce は、値の変化を指定した遅延時間だけ遅らせるHookである。
これは、検索ボックスへの入力等、頻繁に変化する値に対してAPIコールを抑制する用途に使用する。
import { useState, useEffect } from 'react';
/**
* 値の変化を指定した遅延時間だけ遅らせるカスタムHook
* 検索ボックスへの入力等、頻繁に変化する値に対してAPIコール等を抑制する用途に使用する
*
* @template T - 対象となる値の型
* @param value - デバウンス対象の値
* @param delay - 遅延時間 (ミリ秒)
* @returns 遅延後の値
*/
function useDebounce<T>(value: T, delay: number): T {
// デバウンス後の値を管理する状態
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
// 指定した遅延時間後に値を更新するタイマを設定
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// クリーンアップ関数 : 値が変わるたびにタイマをリセット
// これにより、連続して値が変わった場合は最後の変更のみが反映される
return () => clearTimeout(timer);
}, [value, delay]); // valueまたはdelayが変更されたら再実行
// デバウンス後の値を返す
return debouncedValue;
}
// 使用例 : 検索ボックスでのAPI呼び出し最適化
function SearchBox() {
const [inputValue, setInputValue] = useState(''); // 入力値を管理する状態
const debouncedValue = useDebounce(inputValue, 500); // 入力値を500ms遅延させる (入力が止まってから500[ms]後に値が確定)
// デバウンス後の値が変わったらAPIコールを実行
useEffect(() => {
if (debouncedValue) {
// 500ms入力が止まった後にAPIコールを実行する
console.log('検索:', debouncedValue);
}
}, [debouncedValue]);
// 検索入力欄を描画
return <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />;
}
useMediaQuery
useMediaQuery は、CSSメディアクエリの一致状態を監視するHookである。
これは、レスポンシブなUI制御をJavaScript側で行う場合に使用する。
import { useState, useEffect } from 'react';
/**
* CSSメディアクエリの一致状態を監視するカスタムHook
* レスポンシブなUI制御をJavaScript側で行う場合に使用する
*
* @param query - メディアクエリ文字列 (例: '(max-width: 768px)')
* @returns メディアクエリに一致しているかどうかのブール値
*/
function useMediaQuery(query: string): boolean {
// メディアクエリの一致状態を管理
// 初期値は関数形式で設定(SSR対応:windowが存在しない場合はfalse)
const [matches, setMatches] = useState<boolean>(() => {
// サーバサイドレンダリング時はwindowが存在しないためfalseを返す
if (typeof window === 'undefined') return false;
// 初期描画時にメディアクエリの現在の一致状態を取得
return window.matchMedia(query).matches;
});
useEffect(() => {
// SSR時は処理をスキップ
if (typeof window === 'undefined') return;
// MediaQueryListオブジェクトを取得
const mediaQueryList = window.matchMedia(query);
// メディアクエリの一致状態が変化した時に呼ばれるハンドラ
const handleChange = (e: MediaQueryListEvent) => setMatches(e.matches);
// メディアクエリの変化をリッスン開始
mediaQueryList.addEventListener('change', handleChange);
// クリーンアップ関数 : リスナーを削除
return () => mediaQueryList.removeEventListener('change', handleChange);
}, [query]); // クエリが変更されたら再設定
// 現在の一致状態を返す
return matches;
}
// 使用例 : レスポンシブレイアウトの切り替え
function ResponsiveLayout() {
// 画面幅が768[px]以下かどうかを判定
const isMobile = useMediaQuery('(max-width: 768px)');
// 画面サイズに応じて表示を切り替え
return <div>{isMobile ? 'モバイルレイアウト' : 'デスクトップレイアウト'}</div>;
}
useToggle
useToggle は、真偽値の切り替え状態を管理する簡単なHookである。
これは、モーダルの開閉やアコーディオンの展開等、オン / オフの切り替えが必要な場面で使用する。
import { useState, useCallback } from 'react';
/**
* 真偽値の切り替え状態を管理するカスタムHook
* モーダルの開閉やアコーディオンの展開等、オン / オフの切り替えが必要な場面で使用する
*
* @param initialValue - 初期値 (デフォルト: false)
* @returns [現在の値, 切り替え関数] のタプル
*/
function useToggle(initialValue: boolean = false): [boolean, () => void] {
// 現在の真偽値を管理する状態
const [value, setValue] = useState<boolean>(initialValue);
// 値を反転させる関数
// useCallbackでメモ化して、再レンダリング間で同一参照を維持
const toggle = useCallback(() => setValue((prev) => !prev), []);
// 現在の値と切り替え関数をタプルとして返す
// useStateと同じ形式で、分割代入して使用可能
return [value, toggle];
}
// 使用例 : モーダルの開閉制御
function Modal() {
// モーダルの開閉状態を管理
// isOpen : 現在の状態 (初期値はfalse = 閉じている)
// toggleModal : 状態を反転させる関数
const [isOpen, toggleModal] = useToggle(false);
return (
<div>
{/* ボタンクリックでモーダルの開閉を切り替え */}
<button onClick={toggleModal}>
{isOpen ? 'モーダルを閉じる' : 'モーダルを開く'}
</button>
{/* isOpenがtrueの場合のみモーダルを表示 */}
{isOpen && <div className="modal">モーダルコンテンツ</div>}
</div>
);
}
Tauri v2との統合
useTauriCommand
Tauri v2では、バックエンドのRustコマンドをフロントエンドから呼び出す時に、invoke 関数を使用する。
useTauriCommand は、この invoke 関数をラップして非同期処理の状態管理を統一するカスタムHookである。
import { useState, useEffect, useRef } from 'react';
import { invoke } from '@tauri-apps/api/core';
// Tauriコマンド実行結果の型定義
// T: コマンドの戻り値の型
interface UseTauriCommandResult<T> {
result: T | null; // コマンド実行結果 (成功時)
loading: boolean; // ロード中フラグ
error: string | null; // エラーメッセージ (失敗時)
}
// useTauriCommand フックのオプション型定義
// A: コマンド引数の型
interface UseTauriCommandOptions<A> {
command: string; // 呼び出すRustコマンド名
args?: A; // コマンドに渡す引数 (オプション)
shouldExecute?: boolean; // 実行可否フラグ (デフォルト: true)
}
/**
* TauriのRustコマンドを非同期で呼び出すカスタムフック
* ローディング状態とエラーハンドリングを統一的に管理する
*
* @template T - コマンドの戻り値の型
* @template A - コマンド引数の型 (デフォルト: Record<string, unknown>)
* @param options - コマンド名、引数、実行フラグを含むオプションオブジェクト
* @returns 結果、ローディング状態、エラーメッセージを含むオブジェクト
*/
function useTauriCommand<T, A = Record<string, unknown>>(
options: UseTauriCommandOptions<A>
): UseTauriCommandResult<T> {
const { command, args, shouldExecute = true } = options; // オプションから各プロパティを取り出す (shouldExecuteはデフォルトでtrue)
const [result, setResult] = useState<T | null>(null); // コマンド実行結果を管理する状態
const [loading, setLoading] = useState<boolean>(false); // ローディング状態を管理
const [error, setError] = useState<string | null>(null); // エラーメッセージを管理
const isMounted = useRef<boolean>(true); // コンポーネントのマウント状態を追跡 (メモリリーク防止用)
// マウント状態を管理するエフェクト
// クリーンアップ関数でアンマウントを検知
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
// コマンド実行のメイン処理
useEffect(() => {
// 実行フラグがfalseの場合は処理をスキップ
if (!shouldExecute) return;
// 非同期でコマンドを実行する内部関数
const execute = async () => {
setLoading(true); // ローディング開始
setError(null); // エラーをクリア
try {
const data = await invoke<T>(command, args as Record<string, unknown>); // Tauriのinvoke関数でRustコマンドを呼び出し
// マウント中の場合のみ結果をセット (アンマウント後のstate更新を防止)
if (isMounted.current) setResult(data);
}
catch (err) {
// エラーハンドリング : マウント中の場合のみエラーをセット
if (isMounted.current) {
setError(err instanceof Error ? err.message : String(err));
}
}
finally {
// 成功・失敗に関わらずローディング状態を解除
if (isMounted.current) setLoading(false);
}
};
execute();
}, [command, shouldExecute]); // command または shouldExecuteが変更されたら再実行
// 結果をオブジェクトとして返す
return { result, loading, error };
}
// 使用例 : Rustコマンド "get_system_info" を呼び出す
function SystemInfo() {
// useTauriCommandフックを使用してシステム情報を取得
const { result, loading, error } = useTauriCommand<string>({
command: 'get_system_info',
shouldExecute: true,
});
// ローディング中の表示
if (loading) return <p>読み込み中...</p>;
// エラー時の表示
if (error) return <p>エラー: {error}</p>;
// 結果の表示
return <p>{result}</p>;
}
カスタムHookのテスト
カスタムHookは @testing-library/react の renderHook 関数を使用してテストする。
コンポーネントを経由せずにHookの動作を直接検証できる。
- useCounterのテスト
import { renderHook, act } from '@testing-library/react'; import { useCounter } from './useCounter'; // useCounterフックのテストスイート describe('useCounter', () => { // テスト1 : 初期値が正しく設定されることを検証 it('初期値が設定されること', () => { // renderHookでHookをテスト環境で実行 // 引数として初期値10を渡す const { result } = renderHook(() => useCounter(10)); // result.currentでHookの戻り値にアクセス // タプルの1つ目の要素が現在のカウント値 expect(result.current[0]).toBe(10); }); // テスト2 : increment関数で値が1増加することを検証 it('incrementで値が1増加すること', () => { const { result } = renderHook(() => useCounter(0)); // 初期値0でHookを実行 const [, increment] = result.current; // タプルの2つ目の要素がincrement関数 // act関数内で状態更新を伴う操作を実行 // Reactの状態更新を適切に処理するために必須 act(() => { increment(); }); // 増加後の値を検証 expect(result.current[0]).toBe(1); }); // テスト3 : decrement関数で値が1減少することを検証 it('decrementで値が1減少すること', () => { const { result } = renderHook(() => useCounter(5)); // 初期値5でHookを実行 const [, , decrement] = result.current; // タプルの3つ目の要素がdecrement関数 // act関数内でdecrementを実行 act(() => { decrement(); }); // 減少後の値を検証 expect(result.current[0]).toBe(4); }); });
- useFetchのテスト (外部依存のMock化)
fetch等の外部依存をMockに差し替えてテストする場合の例import { renderHook, waitFor } from '@testing-library/react'; import { useFetch } from './useFetch'; // useFetchフックのテストスイート describe('useFetch', () => { // 各テストの前に実行: fetchをMock関数に差し替え beforeEach(() => { // グローバルのfetchをjest.fn()でモック化 // 実際のネットワークリクエストを防止 global.fetch = jest.fn(); }); // 各テストの後に実行 : モックをリセット afterEach(() => { // 全てのモックの状態をクリア // テスト間の干渉を防止 jest.resetAllMocks(); }); // テスト : データを正常に取得できることを検証 it('データを正常に取得すること', async () => { const mockData = { id: 1, name: 'テストユーザ' }; // モックデータを定義 // fetchのモック実装を設定 // mockResolvedValueOnceで1回だけ解決されるPromiseを返す (global.fetch as jest.Mock).mockResolvedValueOnce({ ok: true, // HTTP成功ステータスをシミュレート json: async () => mockData, // JSONパース結果をモック }); // Hookを実行 (型引数でモックデータの型を指定) const { result } = renderHook(() => useFetch<typeof mockData>('/api/user')); // waitForで非同期処理の完了を待機 // ローディングが終了するまで待つ await waitFor(() => { expect(result.current.loading).toBe(false); }); // 取得したデータがモックデータと一致することを検証 expect(result.current.data).toEqual(mockData); // エラーが発生していないことを検証 expect(result.current.error).toBeNull(); }); });
設計原則
単一責任の原則
1つのカスタムHookは、1つの責任のみを持つように設計する。
複数の機能を1つのHookに詰め込むと、再利用性が下がりテストも困難になる。
// 正しい : 責任を分割する
function useUser(userId: string) {
/* ... */
}
function useUserPosts(userId: string) {
/* ... */
}
// 誤り : 複数の責任が混在している
function useUserAndPosts() {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
// ...略
}
依存配列の完全性
useEffect、useCallback、useMemo の依存配列には、内部で参照する全ての値を含める。
ESLintの react-hooks/exhaustive-deps ルールを有効にすることにより、依存配列の漏れを自動的に検出できる。
// 正しい : 参照する全ての値を依存配列に含める
useEffect(() => {
fetchData(query);
}, [query]);
// 誤り : queryが依存配列に含まれていない
useEffect(() => {
fetchData(query);
}, []); // queryの変化が反映されないバグが発生する
クリーンアップの徹底
イベントリスナーの登録、タイマの設定、非同期処理の実行等、副作用を伴う処理には必ずクリーンアップ関数を定義する。
クリーンアップを怠るとメモリリークやアンマウント後のステート更新エラーの原因となる。
useEffect(() => {
const timerId = setInterval(() => {
setCount((prev) => prev + 1);
}, 1000);
// クリーンアップ : コンポーネントのアンマウント時にタイマを解除する
return () => clearInterval(timerId);
}, []);
よくある間違い
useプレフィックスの欠落
use プレフィックスを省略した関数内でHookを呼び出した場合、ESLintが規則違反を検出できなくなる。
// 正しい
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
return isOnline;
}
// 誤り : useプレフィックスがないため、ESLintがHooksルールを検証しない
function getOnlineStatus() {
const [isOnline, setIsOnline] = useState(true); // 規則違反を検出できない
return isOnline;
}
条件付き呼び出し
Hookをif文やループ、早期returnの後で呼び出してはならない。
Reactは呼び出し順序に基づいてHookの状態を管理しているため、条件分岐で呼び出し順序が変わるとステートの対応関係が崩れる。
// 正しい : Hookは常に無条件で呼び出す
function useConditionalHook(condition: boolean) {
const [value, setValue] = useState(0);
// 条件はHookの呼び出し後に使用する
const displayValue = condition ? value : null;
return displayValue;
}
// 誤り : 条件付きでHookを呼び出している
function useConditionalHook(condition: boolean) {
if (condition) {
const [value, setValue] = useState(0); // Hooksのルール違反
}
// ...略
}
クリーンアップ忘れ
イベントリスナーや非同期処理のクリーンアップを忘れると、コンポーネントのアンマウント後にもステート更新が実行されてメモリリークが発生する。
// 正しい : クリーンアップ関数を返す
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// 誤り : クリーンアップがない
useEffect(() => {
window.addEventListener('resize', handleResize);
// クリーンアップがないため、コンポーネントのアンマウント後もリスナーが残る
}, []);
関連情報
- Reactの基礎 - Hooksの基礎
- Reactの基礎 - useState
- Reactの基礎 - useEffect
- Reactの基礎 - useRef
- Reactの基礎 - useContext
- Reactの基礎 - useReducer