useCallbackがかえってコードの意図を曖昧にしていないか
Reactの useCallback
は、関数の再生成を防ぐために導入されることが多いですが、その目的や必要性が不明瞭なまま適用されているケースが多く見られます。レビューアーとしては、useCallbackが本当に意図的に使用されているか、それとも「なんとなく付けられているだけ」かを見極める必要があります。
典型的な構造:明確な理由がなくuseCallbackが使われている
const handleClick = useCallback(() => {
setVisible(prev => !prev);
}, []);
@Reviewerこの関数はコンポーネント内でしか使われておらず、また依存も空配列で固定です。再生成によるコストが低い構造であれば、 `useCallback` は不要です。
関数の再生成を防ぐ目的で useCallback
を使う場合、それが useEffect
の依存になっていたり、 memo
コンポーネントに渡されていたりする合理的な背景がなければ、むしろコードを読みづらくするだけのノイズになります。
useCallbackが必要なパターン
useCallback
が有効に働くのは次のような構造です。
- 子コンポーネントに関数を
props
経由で渡すとき(memo
最適化のため) useEffect
の依存配列に関数が含まれる場合- 関数が高コストの構築処理を含んでおり、再生成を避けたい場合
これらの条件に該当しない場合、レビューアーとしては useCallback
を使っている理由を明示するよう指摘すべきです。
関数を“メモ化すべき”という誤解
実装者の中には「関数は毎回再生成されるから、useCallbackで囲むのがベストプラクティス」と誤って理解しているケースもあります。
const onChange = useCallback((e) => {
setValue(e.target.value);
}, []);
@Reviewerこのような関数はコストも低く、propsにも渡されていないため、メモ化の必要性はありません。`useCallback` があることでむしろ目的が曖昧になります。
このようなパターンは、責務と設計意図が不明瞭な関数抽象を生み出し、保守性や可読性を低下させます。
コールバックを過度に抽象化していないか
関数を useCallback
で囲むことで「何か特別な責務があるように見える」コードになることがありますが、実際には単なるイベントハンドラであれば、明示的に囲う理由は薄いです。
const handleBlur = useCallback(() => {
validate();
}, [validate]);
@Reviewer関数の呼び出ししかしておらず、`validate` が変化する設計でなければ、 `useCallback` は省略しても問題ありません。読みやすさのためにも明確な理由がない限り避ける判断が有効です。
useCallbackをレビューする際の観点
useCallbackに遭遇したとき、レビューアーとしては以下の視点で妥当性を確認していく必要があります。
- propsとして子に渡しているか
- useEffectや他のHookの依存配列に含まれているか
- 高コストな関数か(クロージャ生成や複雑な依存があるか)
- 抽象関数の構造が意図を明確にしているか
不要なuseCallbackを削除したあとの例
function Form() {
const handleSubmit = () => {
doSomething();
};
return <button onClick={handleSubmit}>送信</button>;
}
@Reviewer`handleSubmit` は `useCallback` で囲む必要がない構造です。props経由で渡されていないかぎり、明示的な再生成制御は不要で、むしろ可読性を損なう恐れがあります。
まとめ:useCallbackは構造的意図を表現できているか
useCallbackは「再生成防止」のためだけに存在するわけではなく、その関数がどこに使われ、どんな影響を持つかによって使うかどうかを判断すべきです。レビューアーとしては、次の点を常に確認しましょう。
- その関数はどこに渡されているか?
- 再生成による副作用が存在するか?
- 再生成を避けるための十分な理由があるか?
- コードの意図が逆に曖昧になっていないか?
意図の見えない最適化は、実装者だけでなくレビューアー自身の判断も誤らせます。useCallbackを見かけたら、その存在意義を構造全体から再検証する姿勢が求められます。