Kodokon kodokon.com

カスタムフック: ロジックの抽出と再利用

state を持つロジックを、再利用でき、組み合わせやすく、テスト可能なフックへ抽出します。

8 分 · 3 問

このレッスンを Kodokon で開く

カスタムフックとは、名前が use で始まり、他のフックを呼び出す関数です。共有された state ではなく state を持つロジック を抽出します。それを呼ぶすべてのコンポーネントは、完全に独立した自前の useStateuseEffect を得ます。これはロジックを共通化するための React でもっとも直接的な道具です。HOC や render props がツリーに層を足したのに対し、フックはコンポーネントの本体に滑り込み、簡単に組み合わせられ、テストも容易です。

JAVASCRIPT
import { useEffect, useState } from "react";

function useDebouncedValue(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => {
      setDebounced(value);
    }, delay);
    return () => clearTimeout(id);
  }, [value, delay]);

  return debounced;
}
クリーンアップは変更のたびに前のタイマーをキャンセルします。

use プレフィックスは飾りではありません。これこそが、react-hooks ESLint プラグインがあなたの関数の中でフックのルール - 条件分岐なしの呼び出し、トップレベルでの呼び出し - を検査できる理由です。返す API の形にも気を配りましょう。使い方が useState を映すなら [value, setValue] のタプル、公開するものが 2 つを超えたらすぐに名前付きキーを持つオブジェクトにします。

JAVASCRIPT
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored !== null
      ? JSON.parse(stored)
      : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}
遅延初期化: localStorage はマウント時にのみ読まれます。

フックは組み合わさります。フックは他のフック、あなた自身のフックも含めて呼び出せます。こうして、汎用的な部品の上に、読みやすいビジネス上の抽象が生まれます。AbortController とクリーンアップ関数の組み合わせに注目してください。これがないと、古い入力に対して発火したリクエストが最新のものより後に応答し、その結果を上書きしかねません。検索フィールドの典型的な 競合状態(race condition) です。

JAVASCRIPT
function useUserSearch(query) {
  const debounced = useDebouncedValue(query, 400);
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (debounced === "") {
      setResults([]);
      return;
    }
    const controller = new AbortController();
    fetch("/api/search?q=" + debounced, {
      signal: controller.signal,
    })
      .then((res) => res.json())
      .then(setResults)
      .catch(() => {});
    return () => controller.abort();
  }, [debounced]);
  return results;
}
ビジネス用のフックが汎用フックの上に構築されます。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. カスタムフックの use プレフィックスは実際には何のためにありますか?
    • クラスから受け継いだ、純粋に見た目上の慣習である
    • リンターが関数をフックとして識別し、その中でフックのルールを検査できるようにする
    • React はこのプレフィックスなしでは state を持つ関数の実行を拒否する
    • react からの自動インポートを引き起こす
  2. 2 つのコンポーネントが同じキーで useLocalStorage を呼びます。state を共有しますか?
    • はい。フックがすべての呼び出し側を自動的に同期する
    • いいえ。しかもデータは 2 つの異なるストレージキーに保存される
    • いいえ。各呼び出しは自前の React state を持つ。共有されるのは localStorage 内のデータだけである
  3. useDebouncedValue において、useEffect が返すクリーンアップ関数の役割は何ですか?
    • 保留中のタイマーをキャンセルし、遅延後には最後に入力された値だけが公開されるようにする
    • コンポーネントがアンマウントされるたびに debounce された値をリセットする
    • 遅延の全期間にわたってコンポーネントの再レンダーをブロックする