【実務・中級編】 非同期処理におけるレースコンディションの制御 – React実践ガイド

非同期処理の「裏切り」を制す:useEffectとレースコンディションの戦い方

フロントエンドの現場で「なぜかデータが古いものに書き換わる」「ユーザーの操作と表示が一致しない」というバグに遭遇したことはないだろうか?

特にReactにおいて、`useEffect`で外部APIを叩く際、依存配列の変化に任せてリクエストを投げまくっていると、必ずと言っていいほど「レースコンディション(競合状態)」という名の地獄を見る。今回は、この泥沼から抜け出し、堅牢な非同期処理を実装するための「クリーンアップ関数を使ったフラグ制御」について、プロの視点から解説する。

—

レースコンディションとは何か?

まず、なぜこれが起きるのかを整理しよう。ブラウザのネットワーク処理は「非同期」だ。`useEffect`内で`fetch`を叩いた瞬間、そのリクエストはブラウザのネットワーク層に投げられ、処理は完了するのを待たずに次の行へ進む。

問題は、「リクエストAが完了する前に、依存配列が変化してリクエストBが投げられ、先にリクエストBが完了し、その直後に遅れてリクエストAが完了して古いデータで上書きされる」という状況だ。

ユーザーの目には「検索ワード『React』で検索したのに、なぜかその前に検索した『Vue』の古い結果が表示されている」という、実に不可解で修正が困難なバグとして映る。

—

ベストプラクティス:無視フラグ(Ignore Flag)の設置

この問題を解決する最もエレガントで、かつReactの哲学に沿った手法が「クリーンアップ関数内で状態を無効化する」ことだ。

`useEffect`の返り値であるクリーンアップ関数は、「次の副作用が実行される直前」または「コンポーネントがアンマウントされる直前」に必ず実行される。これを利用して、「このリクエストはもう古いから捨てていいよ」というフラグを立ててやるのだ。

実装サンプル:そのまま使える安全なデータ取得

import { useState, useEffect } from ‘react’;

const UserProfile = ({ userId }) => {
const [data, setData] = useState(null);

useEffect(() => {
// 1. このリクエストが「有効か」を判定するためのフラグを定義
let isIgnored = false;

const fetchData = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const result = await response.json();

// 2. リクエスト完了後にフラグを確認
// もしクリーンアップ関数が先に走っていれば、この処理は無視する
if (!isIgnored) {
setData(result);
}
} catch (err) {
if (!isIgnored) {
console.error(“フェッチ失敗:”, err);
}
}
};

fetchData();

// 3. クリーンアップ関数
// userIdが変更される直前、あるいはコンポーネントが破棄される直前に走る
return () => {
isIgnored = true;
};
}, [userId]); // userIdが変わるたびにリクエストが再起動される

return

{data ? data.name : “読み込み中…”}

;
};

—

この設計がなぜ「プロの現場」で選ばれるのか

初心者のうちは、`useEffect`の中に`AbortController`を使ったり、ステートを複雑に管理しようとしたりする。もちろん、`AbortController`はネットワークレベルで通信を遮断できるため、リソース節約の観点からは非常に強力だ。

しかし、この「フラグ制御」が優れている理由は以下の3点だ。

1. シンプルさ(可読性): 複雑な制御構文を書かなくても、ローカル変数一つで確実に制御できる。
2. 副作用の完結性: `useEffect`というスコープ内で完結しており、外部のステートを汚染しない。
3. 確実性: コンポーネントのライフサイクルと完全に同期しているため、漏れがない。

シニアからのアドバイス:React 18以降の注意点

React 18の`Strict Mode`では、開発環境で`useEffect`が意図的に二重実行される(マウント→アンマウント→再マウント)。この時、このフラグ制御を実装していないと、開発環境だけで謎の挙動に悩まされることになる。

逆に言えば、このフラグ制御を徹底していれば、`Strict Mode`の二重実行すら「コードの堅牢性を確かめるテスト」として活用できるんだ。

—

最後に

「非同期処理を制する者は、Reactを制す」と言っても過言ではない。
APIリクエストは常に「期待通りに返ってこないもの」と疑ってかかるのが、シニアエンジニアの嗜みだ。このフラグ制御は、単なるバグ回避術ではなく、「予測不能な外部世界(ネットワーク)と、予測可能なUI層を繋ぐための防波堤」だと思ってくれ。

まずは手元のプロジェクトで、依存配列を持つ`useEffect`に`isIgnored`フラグを仕込んでみてほしい。それだけで、君のアプリケーションの品質は一歩先へ進むはずだ。

何か詰まったら、いつでも相談してくれ。コードは嘘をつかない、丁寧に書けば必ず応えてくれるものだから。

コメント

タイトルとURLをコピーしました