3秒でわかる
React の状態や副作用の処理をまとめて取り出した、use で始まる自作の関数。同じ処理を複数の部品で使い回し、画面の記述を短く保つために作ります。
もう少し詳しく
どういうものか
カスタムフックは、useState や useEffect を内部で呼ぶ、自分で書いた関数です。名前が use で始まることだけが決まりで、特別な文法はありません。戻り値も自由で、値だけを返しても、値と操作用の関数を組にして返しても構いません。
重要なのは、状態そのものが共有されるわけではないという点です。2 つの部品が同じカスタムフックを呼べば、それぞれ独立した状態を持ちます。共有されるのはロジックであって値ではありません。
なぜ必要か
データ取得を例にすると、読み込み中の表示、エラーの保持、後片付けまで書くと 1 つの部品に 30 行ほど積み上がります。同じ処理が必要な画面が 5 つあれば、その 30 行が 5 か所に散らばります。カスタムフックに切り出せば、画面側は 1 行の呼び出しで済み、再試行の仕組みを足すときも直すのは 1 か所です。
具体例
import { useState, useEffect } from "react";
export function <a href="/glossary/usefetch" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">useFetch</a>(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let alive = true;
setLoading(true);
<a href="/glossary/fetch-api" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">fetch</a>(url)
.then((r) => {
if (!r.ok) throw new Error("status " + r.status);
return r.json();
})
.then((json) => alive && setData(json))
.catch((e) => alive && setError(e))
.finally(() => alive && setLoading(false));
return () => { alive = false; };
}, [url]);
return { data, loading, error };
}
function MemberList() {
const { data, loading, error } = useFetch("/api/members");
if (loading) return <p>読み込み中</p>;
if (error) return <p>取得に失敗しました</p>;
return <ul>{data.map((m) => <li key={m.id}>{m.name}</li>)}</ul>;
}つまずきやすいところ
名前を use で始めないと、フックの規則違反を検出する仕組みが働かなくなります。条件分岐やループの中でフックを呼んでいても警告されず、実行時に状態がずれてから気付くことになります。
もう 1 つは、部品の中で使っていた if の後ろにフックの呼び出しを移してしまうことです。フックは常に同じ順序で呼ばれる前提なので、早期リターンより前に全部呼びます。
似た用語との違い
ただのユーティリティ関数との違いは、内部でフックを呼ぶかどうかです。呼ばないなら use を付けず、普通の関数として書きます。Context は値そのものを共有する仕組みで、複数の部品で同じ状態を見たい場合はカスタムフックではなくこちらを使います。
覚え方
「画面の見た目は部品に、動きの手順はフックに」。この線引きで切り出す対象が決まります。