こんにちは!Reactの世界へようこそ。
HTMLやCSS、JavaScriptの基礎を学んで、いよいよReactで動きのあるアプリを作ろう!と意気込んでいるあなた。本当に素晴らしい挑戦です。
でも、Reactを書き進めていくうちに、こんな「謎の動き」に遭遇したことはありませんか?
- 「ボタンを1回クリックしただけなのに、なぜか処理が2回走ってしまう…」
- 「画面がチカチカして、無限に再読み込みされている気がする…」
- 「コードが複雑になりすぎて、どこで何が起きているのか分からなくなっちゃった…」
これ、実はReactを学び始めた誰もが通る「`useEffect`(ユーエフェクト)の迷宮」なんです。
プロの現場で何年もReactを書いている私たちでも、初心者の頃は同じように頭を抱えていました。だから、全然落ち込む必要はありません。「大丈夫ですよ、一緒に一歩ずつ紐解いていきましょう!」
今回は、React初心者が最もつまずきやすい`useEffect`の「実は使わなくていいケース(アンチパターン)」と、「じゃあどう書けばスッキリ解決するの?」という具体的な回避策を、お買い物の例えなどを交えながら、世界一優しく解説します。
—
そもそも `useEffect` ってなぁに?
難しい専門用語をいったん脇に置いて、身近な例え話から始めましょう。
Reactのコンポーネント(画面の部品)は、いわば「おうちの部屋」です。
部屋の中にある家具を並べ替えたり、壁紙の色を変えたりするのは、Reactが自分の力だけでできる綺麗で安全な作業(これを「レンダリング」と呼びます)です。
一方で、「部屋の外(外部の世界)と連絡を取る作業」もあります。
例えば、
- 遠くのスーパー(サーバー)からデータを取ってくる
- 部屋の窓の外にあるお天気メーター(ブラウザのAPIやタイマーなど)をセットする
このように、Reactのお部屋の外側に影響を与えたり、外の情報を取ってきたりする処理を「副作用(Side Effect)」と呼びます。そして、この「外の世界との通信用ドア」を開ける道具こそが `useEffect` なのです。
ここで大切なルールがあります。
「部屋の中で完結するお片付けなら、わざわざ外へのドア(useEffect)を開ける必要はない」ということです。
ドアを開け閉めするにはエネルギー(パソコンの処理能力)を使います。ドアを開けっ放しにしたり、無駄に行き来したりすると、お部屋の中が散らかる(バグが起きる)原因になってしまうのです。
それでは、よくある「ドアの無駄遣い」のパターンを2つ、見ていきましょう。
—
アンチパターン①:お買い物の「消費税」をわざわざ別で計算してしまう
まずは、お買い物アプリをイメージしてみましょう。
商品の「本体価格」が変わったら、自動的に「消費税込みの価格」も計算して画面に表示したいですよね。
ここで、よくやってしまいがちな「悲しいコード」がこちらです。
❌ やってしまいがちな失敗コード(useEffectの誤用)
import React, { useState, useEffect } from ‘react’;
function ShoppingCart() {
const [price, setPrice] = useState(0); // 商品の本体価格
const [taxIncludedPrice, setTaxIncludedPrice] = useState(0); // 【不要なState】消費税込みの価格
// 😭 悪夢の入り口:priceが変わったら、useEffectで計算し直して別のStateに保存している
useEffect(() => {
setTaxIncludedPrice(price 1.1);
}, [price]); // price(本体価格)を監視している
return (
お買い物カート(バッドパターン)
setPrice(Number(e.target.value))}
placeholder=”本体価格を入力”
/>
消費税込みの価格: {taxIncludedPrice} 円
);
}
export default ShoppingCart;
なぜこれが「ダメ」なのか?
一見、ちゃんと動いているように見えます。しかし、裏側ではReactがこんな風に大忙しでパニックになっています。
1. あなたが価格を「100円」と入力する。
2. Reactが「`price`が100円になったよ!」と画面を書き換える(1回目のレンダリング)。
3. 画面が書き換わった直後、`useEffect`のドアが開き、「あ、`price`が変わったから、`taxIncludedPrice`を110円に更新しなきゃ!」と、`setTaxIncludedPrice`を実行する。
4. Reactが「えっ!またデータが変わったの?じゃあもう一度画面を書き換えるね!」と大慌てで画面を書き換える(2回目のレンダリング)。
そう、たった1回数字を入れただけなのに、画面の書き換えが2回も発生しているのです。
これがお買い物アプリならまだしも、もっと巨大なアプリになると、画面がチカチカしたり、動作が重くなったりする原因になります。
⭕️ スッキリ解決!「その場で計算する」スマートなコード
Reactの黄金ルールのひとつに、「他のState(状態)から計算できる値は、Stateにせず、レンダリングの途中でその場で計算する」というものがあります。
`useEffect`も、2つ目のStateも、丸ごと捨ててしまいましょう!
import React, { useState } from ‘react’;
function ShoppingCart() {
const [price, setPrice] = useState(0); // 必要なStateは「本体価格」これだけ!
// ✨ プロの技:わざわざStateに保存せず、お部屋の中でその場で計算する
const taxIncludedPrice = price 1.1;
return (
お買い物カート(グッドパターン)
setPrice(Number(e.target.value))}
placeholder=”本体価格を入力”
/>
{/ その場で計算された最新の値が、いつでも1回のレンダリングで表示されます /}
消費税込みの価格: {taxIncludedPrice} 円
);
}
export default ShoppingCart;
どうですか? コードがものすごくシンプルになりましたよね!
これなら、価格が変わった瞬間にReactが「1回だけ」画面を綺麗に書き換えてくれます。無駄なドアの開け閉め(`useEffect`)は一切ありません。
—
アンチパターン②:「ボタンが押されたとき」の処理をわざわざuseEffectで監視してしまう
次に、お友達に「メッセージを送信する」ような場面を考えてみましょう。
「送信ボタンがクリックされたら、サーバーにデータを送る」という処理です。
これも、Reactに慣れていないと「Stateが変わったのを`useEffect`で検知して送る」という遠回りな書き方をしてしまいがちです。
❌ やってしまいがちな失敗コード(useEffectの誤用)
import React, { useState, useEffect } from ‘react’;
function MessageSender() {
const [message, setMessage] = useState(”);
const [isSending, setIsSending] = useState(false); // 送信中フラグ
// 😭 危険な罠:送信中フラグが「true」になったのを検知して、送信処理を走らせている
useEffect(() => {
if (isSending) {
// 擬似的にサーバーへデータを送信する処理
console.log(`サーバーに送信しました: ${message}`);
// 送信が終わったらフラグを戻す
setIsSending(false);
}
}, [isSending]); // isSendingの変更を監視している
const handleSendClick = () => {
setIsSending(true); // ボタンが押されたらフラグをtrueにするだけ
};
return (
メッセージ送信(バッドパターン)
setMessage(e.target.value)}
placeholder=”メッセージを入力”
/>
);
}
export default MessageSender;
なぜこれが「ダメ」なのか?
この書き方の最大のデメリットは、「コードの迷子」が生まれることです。
後からこのコードを読んだ人は、こう思うでしょう。
「あれ?ボタンを押したとき(`handleSendClick`)は何をしてるの? …あ、ただフラグを`true`にしてるだけか。じゃあ実際に送信しているのはどこ? …あ、下にスクロールしたところにある`useEffect`の中か!」
これでは、ボタンが押されたときに何が起きるのかを追いかけるために、コードを行ったり来たりしなければなりません。
さらに、もし他の場所で間違えて`setIsSending(true)`が呼ばれてしまったら、ユーザーがボタンを押していないのに勝手にメッセージが送信されてしまうという、恐ろしいバグ(誤送信)に繋がります。
⭕️ スッキリ解決!「イベントハンドラ(クリック関数)の中で直接やる」
「ユーザーの操作(クリックや入力など)がきっかけで起こる処理は、useEffectではなく、イベントハンドラの中に直接書く」。これがReactの王道であり、一番安全な道です。
import React, { useState } from ‘react’;
function MessageSender() {
const [message, setMessage] = useState(”);
// ✨ プロの技:ボタンが押された「その瞬間」にやりたいことをここにまとめる!
const handleSendClick = () => {
if (message.trim() === ”) {
alert(‘メッセージを入力してください’);
return;
}
// 送信処理を「ここ」で直接実行する
console.log(`サーバーに送信しました: ${message}`);
// 送信が終わったら入力欄をスッキリ空にする
setMessage(”);
};
return (
メッセージ送信(グッドパターン)
setMessage(e.target.value)}
placeholder=”メッセージを入力”
/>
{/ クリックされたら、上の関数が真っ直ぐ実行されるので、流れがとても分かりやすい! /}
);
}
export default MessageSender;
どうでしょう!
「ボタンが押されたから、送信する」。ただそれだけのシンプルな因果関係が、ひとつの関数(`handleSendClick`)の中にギュッとまとまりました。これなら、1か月後の自分がコードを見返しても、一瞬で理解できますよね。
—
まとめ:`useEffect` を使わずに済むなら、使わないのが一番美しい
Reactの現場のトップエンジニアたちが口を揃えて言う、魔法の合言葉があります。
> 「最も優れた `useEffect` は、書かれなかった `useEffect` である」
ちょっと極端に聞こえるかもしれませんが、これは本当に真理です。
`useEffect` は「外の世界(サーバー通信やタイマーなど)と繋がるための強力な武器」ですが、強力すぎるがゆえに、使いどころを間違えるとアプリの動きを乱してしまいます。
これからReactを書くときは、`useEffect`を書き始める前に、心の中で一度だけこう問いかけてみてください。
1. 「この値は、今あるStateからその場で計算(足し算やフィルタリングなど)できないかな?」
👉 できるなら、変数に直接代入して計算しましょう!(アンチパターン①の解決策)
2. 「この処理は、ユーザーがボタンをクリックした『その瞬間』にやりたいことかな?」
👉 そうなら、`onClick`などのイベントハンドラの中に直接書きましょう!(アンチパターン②の解決策)
この2つのクエスチョンを意識するだけで、あなたの書くReactコードは驚くほどきれいで、バグのない、プロ顔負けの素晴らしいコードに生まれ変わります。
最初は間違えても当たり前です。何度も無限ループを起こして、画面をフリーズさせて、少しずつ覚えていけばいいんです。この記事が、あなたのReact開発を少しでも優しくサポートできたら嬉しいです。
焦らず、楽しんで、一歩ずつ進んでいきましょう。応援しています!

コメント