カスタム Hook:再利用可能なロジックの抽出
状態を持つロジックをカスタム use* 関数に移し、コードの重複や状態のリフトアップなしにコンポーネント間で共有します。
「カスタム Hook:再利用可能なロジックの抽出」はCoddyKit上の無料Frontend Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはFrontend Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Frontend Academyコースには全4レッスンが含まれています。
カスタムフックとは
カスタムフックは、名前がuseで始まり、内部で他のフックを呼び出すJavaScript関数です。カスタムフックを使うと、状態を持つロジックを取り出して、コピー&ペーストせずに複数のコンポーネントで共有できます。
ロジックをカスタムフックに切り出す
コンポーネント間で重複しているロジック(fetch + loading + error、ウィンドウサイズ、ローカルストレージなど)を見つけ、カスタムフックに移します。
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return width;
}
// Usage:
const width = useWindowWidth();
const isMobile = width < 768;useFetch — データ取得フック
どのコンポーネントからでもURLのデータを読み込める、再利用可能なデータ取得フックです。
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`${res.status}`);
setData(await res.json());
} catch (err) {
if ((err as DOMException).name !== 'AbortError')
setError((err as Error).message);
} finally { setLoading(false); }
}
load();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}useLocalStorageフック
値をlocalStorageと同期し、storageイベントによってタブ間でも同期状態を保つカスタムフックです。
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
const set = useCallback((newValue: T) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
}, [key]);
return [value, set] as const;
}カスタムフックはロジックを共有し、状態は共有しない
カスタムフックを呼び出す各コンポーネントは、それぞれ独立した状態を持ちます。フックはシングルトンではなく、分離された状態のインスタンスを作成する関数呼び出しです。
命名規則
カスタムフックの名前はuseで始める必要があります。これはESLintのreact-hooksリンティングルールによって強制されます。このルールにより、フックがトップレベルで、正しい順序で呼び出されることが保証されます。
複数の値を返す
カスタムフックは通常、オブジェクト(名前付きの値)またはタプル(useStateのようなもの)を返します。タプルでは任意の名前で分割代入できますが、オブジェクトは値の意味がコードから分かりやすくなります。
// Tuple (like useState):
const [value, setValue] = useLocalStorage('key', 0);
// Object (more descriptive):
const { data, loading, error, refetch } = useFetch('/api/users');useDebounceフック
値をデバウンスします。ユーザーが指定した時間入力を止めた後にだけ値を更新します。
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
// Usage:
const debouncedSearch = useDebounce(searchTerm, 300);
useEffect(() => { search(debouncedSearch); }, [debouncedSearch]);useMediaQueryフック
window.matchMediaをフックでラップし、CSSメディアクエリの結果をリアクティブに読み取ります。
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
useEffect(() => {
const mq = window.matchMedia(query);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, [query]);
return matches;
}カスタムフックのテスト
@testing-library/reactのrenderHook()ユーティリティを使うと、コンポーネントでラップせずにカスタムフックを単独でテストできます。
import { renderHook, act } from '@testing-library/react';
test('useCounter increments', () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});公開されているフックライブラリ
コミュニティ製のフックライブラリには、テスト済みのフックが数百種類用意されています: @uidotdev/usehooks、react-use、usehooks-ts。カスタムフックを書く前に、十分にテストされた実装がすでに存在しないか確認してください。
理解度チェック
カスタムフックが2つの異なるコンポーネントから呼び出された場合、フック内部の状態はどうなりますか?
復習: カスタムフック
カスタムフックは、再利用するために状態を持つロジックを切り出します。名前にはuseプレフィックスを付けます。フックを呼び出す各コンポーネントは独立した状態を持ちます。オブジェクトまたはタプルを返します。よくあるパターンには、useFetch、useLocalStorage、useDebounce、useMediaQueryがあります。renderHook()でテストします。一から実装する前に、コミュニティ製ライブラリを確認してください。
よくある質問
「カスタム Hook:再利用可能なロジックの抽出」レッスンは無料ですか?
はい。「カスタム Hook:再利用可能なロジックの抽出」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Frontend Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Frontend Academyコースには全4レッスンが含まれています。
「カスタム Hook:再利用可能なロジックの抽出」で何を学びますか?
状態を持つロジックをカスタム use* 関数に移し、コードの重複や状態のリフトアップなしにコンポーネント間で共有します。 ブラウザで直接実行するハンズオンコードでFrontend Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Frontend Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのFrontend Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「カスタム Hook:再利用可能なロジックの抽出」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このFrontend Academyレッスンでコードを書いて実行できますか?
はい。すべてのFrontend Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- グローバル状態のための useContext
- 複雑な状態のための useReducer
- パフォーマンスのための useMemo と useCallback
- カスタム Hook:再利用可能なロジックの抽出