はじめに

Reactアプリケーションのレビューで、特に注意して見たいのが非同期処理です。
fetch, setTimeout, WebSocket, dispatch(asyncThunk)… など、非同期のロジックがUIコンポーネント内にベタ書きされている構造は、レビューの重点ポイントになります。

この記事では、非同期処理を含む構造をどう見抜き、どう再構成すればよいのかを、レビューアー視点で具体的に解説します。

非同期処理が内包されている兆候

まずは、以下のようなコードがあれば構造的課題を疑ってよいです。

非同期処理が直接埋め込まれている例
export function UserInfo() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(setUser);
@Reviewer
非同期通信を直接UIコンポーネントに書いてしまうと、
構造の責務が曖昧になります。
専用のHookや外部の処理モジュールに切り出すことを検討しましょう。
}, []); return <div>{user?.name}</div>; }
  • API取得は副作用
  • UI層ではなくロジック層で持つべき
  • 将来的なエラー処理・再取得対応が難しくなる

よくある非同期処理の混在パターン

パターン 構造の課題 改善方向
useEffect内にfetch直書き UI責務とロジック責務の混在 カスタムHookで分離
onClickイベントにasync処理直書き UIイベントとドメイン操作の密結合 モジュール化 or useAsyncなどの抽象化
Redux dispatch(asyncThunk) を直接書く 再利用しにくく、依存関係が不明瞭 処理サービス層に移譲

構造分離の指針

パターン1:データ取得はCustom Hookへ

useUser.ts
export function useUser() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(setUser);
  }, []);
  return user;
}
UserInfo.tsx
const user = useUser();
return <div>{user?.name}</div>;
  • useUserの中で副作用を完結させ、UIから副作用を切り離す
  • テストや構造理解が圧倒的にしやすくなる

パターン2:非同期イベントはuseCallback+抽象モジュールへ

useSaveUser.ts
export async function saveUser(data) {
  const res = await fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(data),
  });
  return await res.json();
}
ProfileForm.tsx
const handleSave = useCallback(async () => {
  const result = await saveUser(form);
  alert(result.status);
}, [form]);
  • UIイベントはあくまでトリガーのみ
  • 非同期の本体は抽象化された外部関数で定義

構造の違いを図示

UML Diagram
UML Diagram

構造的に非同期処理がUIから切り離されている後者の方が、責務の明確さ・拡張性・テスト容易性の面ですぐれています。

Redux環境でのレビュー観点

ReduxやRTK環境でも、dispatch(fetchUser()) のような記述が直接UIコンポーネント内にある場合、以下のような構造的問題が発生しやすいです。

  • どのタイミングでdispatchされるかが隠蔽されやすい
  • 複数のdispatchが並列で走って順序があいまいになる
  • テスト時にstoreのモックが面倒になる

改善案

useFetchUser.ts
export const useFetchUser = () => {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchUser());
  }, [dispatch]);
};
UserContainer.tsx
useFetchUser();
return <UserInfo />;

このように、非同期のトリガー側をHookに閉じ込めるだけでも構造の明瞭さが増します。

レビューコメント例

非同期処理混在の指摘
@Reviewer: fetchをUI内に記述する構造は、責務の混在と再利用性の低下を招きます。useXxxの形で切り出し、処理の責務を明示的に分離しましょう。
提案コメント例
- useEffect(() => { fetch(...); }, []);
+ const user = useUser();

まとめ

Reactで非同期処理を書くのは自然なことですが、それがUI構造と一体化してしまうと、コードの可読性・保守性が大きく下がります。

レビューアーとしては、「そのfetch、このコンポーネントで書くべき?」「非同期の流れが明確か?」という視点で、
責務の分離・構造の再設計を提案していくことが重要です。

非同期ロジックこそ、構造レビューで真っ先に見抜きたい設計要素のひとつです。