カスタムHookを導入すべき判断基準:再利用と切り出しのバランス
ReactのカスタムHook(useXXX
)は、状態ロジックや副作用処理の再利用を目的としてよく使われますが、すべてのロジックをカスタムHookに切り出せば良いというわけではありません。
レビューアーは「このHookは本当に分離する価値があるのか」「コンポーネントの責務として留めるべきだったのではないか」という視点で構造を評価する必要があります。
よくある切り出し理由とその妥当性
開発者がカスタムHookを作成する際、次のような動機を持っていることが多いです。
- 1つのコンポーネントが肥大化してきた
- 別のコンポーネントでも同じロジックを使いたくなった
- 特定の状態や副作用を1つにまとめておきたい
- テストしやすくなると考えた
これらはいずれも正当な理由のように見えますが、責務の切り方が曖昧なままHook化されると、逆にコードの可読性や把握性が低下する場合があります。
典型的な切り出し例とそのレビュー
function ProductTable() {
const [products, setProducts] = useState([]);
const [isLoading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch("/api/products")
.then(res => res.json())
.then(setProducts)
.catch(setError)
.finally(() => setLoading(false));
}, []);
}
この構造はデータ取得ロジックがコンポーネント内に存在し、複数の状態変数と非同期処理が混在しています。以下のようにカスタムHook化すると分離はされますが、それが「適切な責務分離かどうか」はレビューによって判断されるべき点です。
function useProducts() {
const [products, setProducts] = useState([]);
const [isLoading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch("/api/products")
.then(res => res.json())
.then(setProducts)
.catch(setError)
.finally(() => setLoading(false));
}, []);
return { products, isLoading, error };
}
function ProductTable() {
const { products, isLoading, error } = useProducts();
}
カスタムHookで責務は分離されていますが、「再利用の見込み」や「外部依存性の抽象化」が無い場合、責務の分断になっている可能性があります。構造上、他の用途でも再利用できる見込みがあるかを確認すべきです。
カスタムHook導入の判断ポイント
次の条件がある程度満たされている場合、カスタムHookの導入が設計上有効と判断できます。
- 複数のコンポーネントで共通のロジックがある
- 外部依存(API、ストレージなど)を抽象化したい
- 状態・副作用・イベントなど複数要素を一括で扱いたい
- 親コンポーネントの責務から分離した方がUIの整理につながる
これらが満たされない場合は、「とりあえず分離」のようなカスタムHookが生まれ、かえって構造が断片化してしまう原因となります。
機能の断片化を避けるために
レビューアーとしては、Hookの責務が以下のように不明確な構造に陥っていないかを読み解く必要があります。
const { ready, value } = useSomething();
@ReviewerカスタムHookの責務が抽象的で、どのような状態管理や副作用を含んでいるのかが名前から判断できません。明確な責務単位で切り出されているかを確認すべきです。
このような構造では、useSomething
がどのような責務を持っているのか読み取りづらく、使用側では構造の内部理解が必須になります。
名前付けから読み取れる責務か?
命名規則もHookのレビューでは非常に重要です。例えば:
useFormState
→ フォームの状態を管理しているuseFetchUser
→ ユーザー情報の取得を行っているuseAuthGuard
→ 認可処理を担っている
このように役割が名前で伝わることが、構造的な理解の助けになります。逆に以下のような命名は避けるべきです。
useLogic
useProcess
useControl
useSomething
Hook内に責務が混在していないか
カスタムHookが次のような複合構造になっている場合は、分割も検討すべきです。
function useUserFeature() {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetchUser().then(setUser).catch(setError);
}, []);
const isAdmin = user?.role === "admin";
const canEdit = isAdmin && user?.permissions.includes("write");
return { user, isAdmin, canEdit };
}
@Reviewerデータ取得、ロール判定、権限計算が1つのHookに詰め込まれており、責務が重複しています。useUserFetch / useUserRole / useUserPermissionなどに責務を分ける方針も検討すべきです。
まとめ:レビューアーの観点チェックリスト
- 複数箇所で再利用されている、または今後再利用されそうか?
- Hookの責務が単一で明確か?命名で意図が伝わるか?
- 状態管理・副作用・ロジックが混在していないか?
- UI側に置いておくべき処理まで巻き取っていないか?
- 切り出すことで構造が読みやすくなっているか?
カスタムHookは便利で強力なツールですが、責務の単位が見えにくくなった瞬間に設計の境界を破壊する存在にもなりえます。レビューでは「切り出すべきか、留めるべきか」という構造判断にしっかりと立脚し、可読性と再利用性の両立を目指す必要があります。