state を持つロジックを、再利用でき、組み合わせやすく、テスト可能なフックへ抽出します。
このレッスンを Kodokon で開くカスタムフックとは、名前が use で始まり、他のフックを呼び出す関数です。共有された state ではなく state を持つロジック を抽出します。それを呼ぶすべてのコンポーネントは、完全に独立した自前の useState と useEffect を得ます。これはロジックを共通化するための React でもっとも直接的な道具です。HOC や render props がツリーに層を足したのに対し、フックはコンポーネントの本体に滑り込み、簡単に組み合わせられ、テストも容易です。
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 つを超えたらすぐに名前付きキーを持つオブジェクトにします。
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];
}フックは組み合わさります。フックは他のフック、あなた自身のフックも含めて呼び出せます。こうして、汎用的な部品の上に、読みやすいビジネス上の抽象が生まれます。AbortController とクリーンアップ関数の組み合わせに注目してください。これがないと、古い入力に対して発火したリクエストが最新のものより後に応答し、その結果を上書きしかねません。検索フィールドの典型的な 競合状態(race condition) です。
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;
}