【入門編】 useEffect内での非同期処理の扱い – React実践ガイド

こんにちは!Reactの学習、楽しく進められていますか?
コンポーネントを作って、画面にボタンや文字が表示されるようになると、「よし、次はデータをサーバーから取ってこよう!」とワクワクしますよね。

そんな時、避けて通れないのが「useEffect」というフックです。

このuseEffectの中で非同期処理(データの取得など)をやろうとしたとき、多くの人が一度は「あれ?」とつまずくポイントがあります。それが、「useEffectのコールバック関数を `async` にしてはいけない」というルールです。

「えっ、サーバーからデータを取るときは `async/await` が使いたいのに、どうしてダメなの?」
「じゃあ、どうやって書けばいいの?」

今回は、そんな疑問やモヤモヤを、身近な例え話と一緒にスッキリ解消していきましょう!大丈夫、一歩ずつ見ていけば絶対に理解できますからね。

—

なぜ、useEffect自体を `async` にしてはいけないの?

まずは、なぜ「`useEffect(async () => { … })`」と書いてはいけないのか、その理由をイメージしてみましょう。

Reactにとって、useEffectは「コンポーネントが画面に描画されたあと、こっそり裏で片付けや準備をしてくれるお助けマン」です。
そして、useEffectのもう一つの大事なお仕事として「クリーンアップ(お片付け)」という役割があります。例えば、「画面からいなくなる時に、タイマーを止める」「通信をキャンセルする」といったお片付け関数を、useEffectは“即座に”受け取りたいのです。

ここで、身近な例えを一つ。

> 【レストランの注文の例え】
> あなたがレストランの店員さん(React)に、「お水を持ってきて(副作用の実行)」と頼みました。
> そして、「もしお水がなくなったら、このグラスを下げてね(クリーンアップ関数)」というお片付けのルールも一緒に伝えておきたいとします。
>
> ところが、店員さんに「async(非同期)」という魔法の呪文をかけてしまうと、店員さんは「かしこまりました!ただいま裏でゴソゴソと作業をしてきますので、結果が出るまで少し待ってください!」と言って、今すぐ結果(Promiseオブジェクト)を返さなくなってしまいます。
>
> React「あれっ? 今すぐお片付けのルール(関数)が欲しかったのに、未来の約束手形(Promise)が返ってきたぞ……これじゃお片付けできないよ!」と困ってしまうわけです。

Reactは「すぐに実行して、すぐにお片付け関数を返してほしい」のに、`async` な関数は「Promise(未来の約束)」という別のものを返してしまう。これが、useEffectに直接 `async` をつけてはいけない本当メンタルの理由なんです。

—

じゃあ、どう書けばいいの?(推奨パターン)

理由が分かったところで、「じゃあどうやって非同期処理を書けばいいの?」という疑問に答えましょう。

答えはシンプルです。
「useEffectの外側ではなく、useEffectの『中』に、別の `async` 関数を作って、その場で呼び出す」 という方法をとります。

先ほどのレストランの例えで言うなら、
「店員さん自身をasyncにするのではなく、店員さんが『よーし、今から厨房に行って料理を取ってくるぞ』という専用の小さな作業員(async関数)をその場で作って、すぐに実行させる」というイメージです。

実際のコードを見てみましょう。お買い物で商品データをサーバーから取ってくるシチュエーションを想像してください。

import React, { useState, useEffect } from ‘react’;

function ItemList() {
// 商品リストを入れておくための「箱(state)」を用意します
const [items, setItems] = useState([]);
// 読み込み中かどうかを判定する箱
const [loading, setLoading] = useState(true);

useEffect(() => {
// 1. useEffectの「中」で、asyncな関数を新しく定義します(名前は何でもOK!ここでは fetchItems)
const fetchItems = async () => {
try {
// サーバーからデータを取ってくるフリ(擬似的な非同期処理)
const response = await fetch(‘https://api.example.com/items’);
const data = await response.json();

// 2. 取ってきたデータを箱に入れます
setItems(data);
} catch (error) {
console.error(‘データの取得に失敗しちゃいました…:’, error);
} finally {
// 3. 成功しても失敗しても、読み込み中は終了!
setLoading(false);
}
};

// 4. 定義した関数を、その場ですぐ実行します!
fetchItems();

}, []); // 依存配列が空なので、このコンポーネントが最初に画面に出た時だけ実行されます

// 読み込み中のときの表示
if (loading) {
return

お気に入りの商品を探しています…少々お待ちくださいね!

;
}

// データが取れたあとの表示
return (

    {items.map(item => (

  • {item.name}
  • ))}

);
}

export default ItemList;

このコードのグッとくるポイント

1. useEffect自体は `async` になっていない
`useEffect(() => { … }, [])` の部分は通常の関数(同期関数)のままなので、Reactは怒りません。
2. useEffectの中で関数を作って、すぐに呼んでいる
`const fetchItems = async () => { … }` と定義したあと、その下の行で `fetchItems();` と即座に実行しています。「定義して、すぐ発動!」これが黄金パターンです。

—

もう一歩踏み込む:クリーンアップと非同期処理の注意点

実務でコードを書いていると、「データを取っている途中に、ユーザーが別のページに移動しちゃった!」という状況に出くわします。

そんなとき、すでに消えてしまったコンポーネントのデータを無理やり更新しようとすると、Reactから「メモリリーク(無駄なメモリ消費)の危険があるよ!」と警告を怒られてしまいます。

そんな時のために、useEffectのクリーンアップ関数を使って、「画面から消えたときは、もうデータ取得の続きをしなくていいよ」と伝えてあげるのがプロの技です。

useEffect(() => {
let isCancelled = false; // 「今、この画面にいますよ」というフラグ

const fetchData = async () => {
const data = await api.getData();

// もしデータが届く前にユーザーが画面を離れていたら、Stateを更新しない!
if (!isCancelled) {
setData(data);
}
};

fetchData();

// クリーンアップ関数(このコンポーネントが画面から消えるときに呼ばれます)
return () => {
isCancelled = true; // 「もう画面にいないよ」とフラグを切り替える
};
}, []);

このように、フラグを一枚挟むだけで、アプリの動作がぐっと安定してエラーに強いコードになります。

—

まとめ

  • useEffectのコールバック自体を `async` にしてはいけない理由

Reactがお片付け関数(クリーンアップ)を即座に受け取れなくなってしまうから。

  • 正しい書き方

useEffectの「中」で `async` な関数を定義し、その場で呼び出す。

最初は「なんでこんな回りくどい書き方をしなきゃいけないんだろう?」と戸惑うかもしれませんが、何度か書いていくうちに「あ、Reactとおしゃべりするためのルールなんだな」と自然に手が動くようになります。

もしコードを書いていてエラーが出ても、焦らなくて大丈夫。一つずつ仕組みを紐解いていけば、必ず思い通りの動きを作れるようになりますよ。
あなたのReactライフを、心から応援しています!

コメント

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