MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Reactの基礎 - カスタムHookのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Reactの基礎 - カスタムHook
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == カスタムHookは、コンポーネント間でステートフルなロジックを再利用するための仕組みである。<br> <code>useState</code>、<code>useEffect</code> 等の組み込みHookを組み合わせることにより、複雑な処理をカプセル化して、複数のコンポーネントから呼び出せる独自の関数として定義できる。<br> <br> カスタムHookの命名規則として、関数名は必ず <code>use</code> で始める必要がある。<br> (例: <code>useOnlineStatus</code>、<code>useLocalStorage</code>)<br> <br> <u>この規則は、ESLintのHooksプラグインがカスタムHookを検出するための判定基準でもあり、省略するとHooksのルール違反を検出できなくなるため厳守する必要がある。</u><br> <br> カスタムHookの重要な特性として、同じHookを複数のコンポーネントで使用しても、各コンポーネントが保持するステートは独立している。<br> コンポーネントAとコンポーネントBが同じ <code>useCounter</code> を呼び出しても、それぞれのカウンター値は独立して管理される。<br> <br> カスタムHookを活用することで、以下に示すメリットが得られる。<br> <br> <center> {| class="wikitable" |+ カスタムHookのメリット ! 項目 !! 説明 |- | ロジックの再利用 || 同じステートフルなロジックを複数コンポーネントで共有できる。 |- | 関心の分離 || UIとロジックを分離して、コンポーネントをシンプルに保てる。 |- | テスト容易性 || ロジック単体を <code>renderHook</code> でテストできる。 |- | 可読性の向上 || 複雑な処理に意味のある名前を付けてカプセル化できる。 |} </center> <br><br> == カスタムHookの基本 == ==== 命名規則 ==== ReactのカスタムHook関数の名前は、必ず <code>use</code> プレフィックスで始めるキャメルケース (camelCase) で記述する。<br> <br> 下表に、命名の正誤例を示す。<br> <br> <center> {| class="wikitable" |+ カスタムHook命名の正誤例 ! 命名例 !! 判定 !! 説明 |- | <code>useOnlineStatus</code> || 正しい || <code>use</code> プレフィックスがある。 |- | <code>getOnlineStatus</code> || 誤り || <code>use</code> プレフィックスがなく、ESLintが検出できない。 |- | <code>useLocalStorage</code> || 正しい || <code>use</code> プレフィックスがある。 |- | <code>localStorageHook</code> || 誤り || <code>use</code> プレフィックスがなく、Hooksのルール違反を検出できない。 |- | <code>useFetch</code> || 正しい || <code>use</code> プレフィックスがある。 |} </center> <br> ESLintの <code>eslint-plugin-react-hooks</code> プラグインは、<code>use</code> プレフィックスを持つ関数内でのみHooksのルール (条件付き呼び出し禁止等) を検証する。<br> <code>use</code> プレフィックスを省略すると、この検証が機能しなくなり、バグの検出が困難になる。<br> <br> ==== 基本的な構造 ==== カスタムHookは、<code>useState</code> や <code>useEffect</code> を組み合わせてロジックをカプセル化して、必要な値や関数を返す。<br> <br> オンライン状態を監視するカスタムHookの例を以下に示す。<br> <br> <syntaxhighlight lang="typescript"> 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>; } </syntaxhighlight> <br><br> == TypeScriptでの型定義 == ==== 引数と戻り値の型 ==== TypeScriptでカスタムHookを定義する場合は、引数と戻り値の型を明示することで、使用側での型安全性が向上する。<br> <br> * カウンタHookの型定義の例 *: <syntaxhighlight lang="typescript"> 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> ); } </syntaxhighlight> *: <br> * ジェネリック型を使用することで、型を柔軟に指定できる再利用可能なHookを定義できる。 *: <syntaxhighlight lang="typescript"> 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]; } </syntaxhighlight> <br> ==== 戻り値の設計 ==== カスタムHookの戻り値には、タプル形式とオブジェクト形式の2種類があり、戻り値の数と用途によって使い分ける。<br> <br> 下表に、それぞれの特徴と使い分けの指針を示す。<br> <br> <center> {| class="wikitable" |+ タプル形式 と オブジェクト形式の比較 ! 項目 !! タプル形式 !! オブジェクト形式 |- | 戻り値の数 || 2〜3個程度が適切 || 4個以上に対応しやすい。 |- | 命名の自由度 || 分割代入時に任意の名前を付けられる。 || プロパティ名が固定される。 |- | IDE補完 || 型の順序に依存する。 || プロパティ名で補完が効きやすい。 |- | 主な用途 || <code>useState</code> や <code>useReducer</code> と同様の形式 || 戻り値の意味を明確にする場合 |} </center> <br> オブジェクト形式の戻り値の例を以下に示す。<br> <br> <syntaxhighlight lang="typescript"> 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> ); } </syntaxhighlight> <br><br> == サンプルコード : カスタムHook == ==== useLocalStorage ==== <code>useLocalStorage</code> は、ローカルストレージとReactのステートを自動的に同期するHookである。<br> <br> 以下の例では、他タブからの変更を検知するために <code>storage</code> イベントを購読している。<br> <br> <syntaxhighlight lang="typescript"> 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]; } </syntaxhighlight> <br> ==== useFetch ==== <code>useFetch</code> は、データフェッチングの状態 (<code>data</code>、<code>loading</code>、<code>error</code>) を管理するHookである。<br> <br> 以下の例では、<code>ignore</code> フラグを使用して、コンポーネントのアンマウント後のステート更新を防止している。<br> <br> <syntaxhighlight lang="typescript"> 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 }; } </syntaxhighlight> <br> ==== useDebounce ==== <code>useDebounce</code> は、値の変化を指定した遅延時間だけ遅らせるHookである。<br> <u>これは、検索ボックスへの入力等、頻繁に変化する値に対してAPIコールを抑制する用途に使用する。</u><br> <br> <syntaxhighlight lang="typescript"> 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)} />; } </syntaxhighlight> <br> ==== useMediaQuery ==== <code>useMediaQuery</code> は、CSSメディアクエリの一致状態を監視するHookである。<br> これは、レスポンシブなUI制御をJavaScript側で行う場合に使用する。<br> <br> <syntaxhighlight lang="typescript"> 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>; } </syntaxhighlight> <br> ==== useToggle ==== <code>useToggle</code> は、真偽値の切り替え状態を管理する簡単なHookである。<br> これは、モーダルの開閉やアコーディオンの展開等、オン / オフの切り替えが必要な場面で使用する。<br> <br> <syntaxhighlight lang="typescript"> 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> ); } </syntaxhighlight> <br><br> == Tauri v2との統合 == ==== useTauriCommand ==== Tauri v2では、バックエンドのRustコマンドをフロントエンドから呼び出す時に、<code>invoke</code> 関数を使用する。<br> <br> <code>useTauriCommand</code> は、この <code>invoke</code> 関数をラップして非同期処理の状態管理を統一するカスタムHookである。<br> <br> <syntaxhighlight lang="typescript"> 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>; } </syntaxhighlight> <br><br> == カスタムHookのテスト == カスタムHookは <code>@testing-library/react</code> の <code>renderHook</code> 関数を使用してテストする。<br> コンポーネントを経由せずにHookの動作を直接検証できる。<br> <br> * useCounterのテスト *: <syntaxhighlight lang="typescript"> 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); }); }); </syntaxhighlight> *: <br> * useFetchのテスト (外部依存のMock化) *: <code>fetch</code> 等の外部依存をMockに差し替えてテストする場合の例 *: <syntaxhighlight lang="typescript"> 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(); }); }); </syntaxhighlight> <br><br> == 設計原則 == ==== 単一責任の原則 ==== <u>1つのカスタムHookは、1つの責任のみを持つように設計する。</u><br> <br> 複数の機能を1つのHookに詰め込むと、再利用性が下がりテストも困難になる。<br> <br> <syntaxhighlight lang="typescript"> // 正しい : 責任を分割する function useUser(userId: string) { /* ... */ } function useUserPosts(userId: string) { /* ... */ } // 誤り : 複数の責任が混在している function useUserAndPosts() { const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); // ...略 } </syntaxhighlight> <br> ==== 依存配列の完全性 ==== <code>useEffect</code>、<code>useCallback</code>、<code>useMemo</code> の依存配列には、内部で参照する全ての値を含める。<br> ESLintの <u>react-hooks/exhaustive-deps</u> ルールを有効にすることにより、依存配列の漏れを自動的に検出できる。<br> <br> <syntaxhighlight lang="typescript"> // 正しい : 参照する全ての値を依存配列に含める useEffect(() => { fetchData(query); }, [query]); // 誤り : queryが依存配列に含まれていない useEffect(() => { fetchData(query); }, []); // queryの変化が反映されないバグが発生する </syntaxhighlight> <br> ==== クリーンアップの徹底 ==== イベントリスナーの登録、タイマの設定、非同期処理の実行等、副作用を伴う処理には必ずクリーンアップ関数を定義する。<br> <br> クリーンアップを怠るとメモリリークやアンマウント後のステート更新エラーの原因となる。<br> <br> <syntaxhighlight lang="typescript"> useEffect(() => { const timerId = setInterval(() => { setCount((prev) => prev + 1); }, 1000); // クリーンアップ : コンポーネントのアンマウント時にタイマを解除する return () => clearInterval(timerId); }, []); </syntaxhighlight> <br><br> == よくある間違い == ==== useプレフィックスの欠落 ==== <code>use</code> プレフィックスを省略した関数内でHookを呼び出した場合、ESLintが規則違反を検出できなくなる。<br> <br> <syntaxhighlight lang="typescript"> // 正しい function useOnlineStatus() { const [isOnline, setIsOnline] = useState(true); return isOnline; } // 誤り : useプレフィックスがないため、ESLintがHooksルールを検証しない function getOnlineStatus() { const [isOnline, setIsOnline] = useState(true); // 規則違反を検出できない return isOnline; } </syntaxhighlight> <br> ==== 条件付き呼び出し ==== Hookをif文やループ、早期returnの後で呼び出してはならない。<br> Reactは呼び出し順序に基づいてHookの状態を管理しているため、条件分岐で呼び出し順序が変わるとステートの対応関係が崩れる。<br> <br> <syntaxhighlight lang="typescript"> // 正しい : 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のルール違反 } // ...略 } </syntaxhighlight> <br> ==== クリーンアップ忘れ ==== イベントリスナーや非同期処理のクリーンアップを忘れると、コンポーネントのアンマウント後にもステート更新が実行されてメモリリークが発生する。<br> <br> <syntaxhighlight lang="typescript"> // 正しい : クリーンアップ関数を返す useEffect(() => { window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // 誤り : クリーンアップがない useEffect(() => { window.addEventListener('resize', handleResize); // クリーンアップがないため、コンポーネントのアンマウント後もリスナーが残る }, []); </syntaxhighlight> <br><br> == 関連情報 == * [[Reactの基礎 - Hooksの基礎]] * [[Reactの基礎 - useState]] * [[Reactの基礎 - useEffect]] * [[Reactの基礎 - useRef]] * [[Reactの基礎 - useContext]] * [[Reactの基礎 - useReducer]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板,React,JavaScript,TypeScript,TSX,カスタムHook,Custom Hook,useState,useEffect,useCallback,useMemo,useLocalStorage,useFetch,useDebounce,useMediaQuery,useToggle,Tauri,renderHook,テスト |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]][[カテゴリ:Web]]
Reactの基礎 - カスタムHook
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse