【入門編】 参照型(オブジェクト・配列)の依存配列における注意点 – React実践ガイド

こんにちは!Reactの学習、毎日お疲れ様です。

画面に文字やボタンを表示させて、少しずつ自分の思い通りに動くようになってくると、「おっ、Reactって面白いかも!」ってワクワクしてきますよね。でも、その楽しさの裏で、誰もが一度は通る「魔の壁」があるんです。

それが今回お話しする「`useEffect`の依存配列にオブジェクトや配列を置いちゃうと、なぜか無限ループ地獄が始まる問題」です。

コンソール画面がものすごい勢いで真っ赤に染まったり、ブラウザが急に重くなったりして、「うわっ、なんか壊しちゃった!?」と冷や汗をかいた経験、ありませんか?
大丈夫です。あなたが何かおかしなコードを書いたわけでも、Reactに嫌われているわけでもありません。みんな最初はここで盛大につまずくんです。

今日は、なぜこの現象が起きるのか、身近な例えを交えながら一緒に優しく解きほぐしていきましょう!

—

1. なぜ「無限ループ」が起きちゃうの?

まずは、Reactの気持ちになって考えてみましょう。

`useEffect`というのは、いわば「お見張り番」のようなものです。依存配列(`[]`の中身)に指定したものが「おっ、変わったぞ!」と変化したときだけ、中身の処理を実行してくれます。

ここで、「お買い物メモ(オブジェクト)」を例に出してみましょう。

あなたは今日、スーパーに買い出しに行く予定です。

  • 「牛乳と卵を買う」というメモ(オブジェクト)をポケットに入れています。

Reactくん(お見張り番)は、あなたのポケットをじーっと見てこう言っています。
> 「ボス、ポケットの中の『メモ用紙』が変わったら教えてくださいね。そしたら新しい指示を実行しますから!」

さて、ここで問題です。あなたがコンポーネントの中で、次のようなコードを書いたとします。

// コンポーネントが再レンダリングされるたびに作られるオブジェクト
const shoppingMemo = { item: “牛乳と卵” };

useEffect(() => {
console.log(“メモが変わったので、処理を実行します!”);
}, [shoppingMemo]); // 「このメモを監視してね」

一見、「同じ内容のメモなんだから、変わってないじゃん!」って思いますよね。人間ならそう思います。
でも、JavaScriptの世界(そしてReactの世界)では、まったく違うルールが働いているんです。

JavaScriptの「別人のそっくりさん」ルール

JavaScriptにとって、オブジェクトや配列というのは、いわば「毎回新しく作り直される使い捨ての付箋紙」です。

コンポーネントが画面に表示されるとき(レンダリングされるとき)、JavaScriptは毎回、新品の付箋紙に「牛乳と卵」と書き直してポケットに入れます。

  • 1回目のレンダリング:ポケットの中に「付箋A(牛乳と卵)」が入る。Reactくん「よし、確認!」
  • 画面のどこかが変わって、コンポーネントが再レンダリングされる:ポケットの中身が「付箋B(中身は同じく牛乳と卵だけど、新しく作った別物)」にすり替わる。

ここでReactくんは、こう思います。
> 「あれっ!? さっきまでポケットに入ってた『付箋A』と、今入っている『付箋B』は、中身が文字通り同じでも、モノとしては別の紙(別の参照場所)だぞ! メモが変わった!!」

Reactくんは真面目なので、メモが変わったと勘違いして、`useEffect`の中の処理を実行します。
すると、その処理の中で「画面の状態(state)」が更新され、それがきっかけでまたコンポーネントが再レンダリングされます。

再レンダリングされると…また新しい「付箋C」が作られます。
Reactくん「またメモが変わった!!(`useEffect`発動)」
→ 状態更新 → 再レンダリング → 新しい「付箋D」が作られる
→ Reactくん「また変わった!!(無限ループの完成 🎉)」

これが、オブジェクトや配列を依存配列に入れたときに起きる無限ループの正体です。

—

2. じゃあ、どうやって防ぐの?(解決策のステップ)

この無限ループを防ぐには、大きく分けて2つのアプローチがあります。
初心者の方に一番おすすめなのは、「オブジェクトや配列そのものを監視するのではなく、中身のプリミティブな値(文字列や数値など)だけを監視する」という方法です。

解決策A:オブジェクト全体ではなく、中の「プロパティ」を監視する

先ほどの例なら、オブジェクトを丸ごと監視するのをやめて、中身の文字列やIDだけに分解して依存配列に渡してあげましょう。

import { useState, useEffect } from ‘react’;

function ShoppingComponent() {
const [count, setCount] = useState(0);

// オブジェクトではなく、監視したい「値そのもの」を定義する
const itemName = “牛乳と卵”;

useEffect(() => {
// 文字列(プリミティブ型)は、中身が同じなら「同じもの」と判定されます!
console.log(“お買い物の品目変わった?いや、文字だから安全だよ!”);
}, [itemName]); // 👈 オブジェクトではなく、文字列を置く

return (

現在のカウント: {count}

);
}

これなら、ボタンを押してコンポーネントが何回再レンダリングされても、`itemName`はただの文字列「牛乳と卵」なので、Reactくんは「おっ、変わってないな」と安心して、無限ループを回避できます。

—

解決策B:どうしてもオブジェクトや配列を渡したいときの奥の手 `useMemo`

「いや、アプリの設計上、どうしてもオブジェクトのまま渡したいんだよ!」という場合もありますよね。そんなときの頼もしい相棒が、`useMemo`というフックです。

`useMemo`は、言ってみれば「メモ用紙のコピーガード機能」です。
「中身が同じなら、新しく紙を作り直さずに、さっき作った古い紙をそのまま使い回してね」とReactにお願いすることができます。

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

function SmartComponent() {
const [count, setCount] = useState(0);

// useMemoを使って、オブジェクトの「住所(参照)」を固定する
const shoppingMemo = useMemo(() => {
return { item: “牛乳と卵” };
}, []); // 👈 依存配列を空にしておくと、このオブジェクトは最初の一回だけ作られ、ずっと使い回されます

useEffect(() => {
console.log(“このエフェクトは安全に実行されます!”);
}, [shoppingMemo]); // 👈 useMemoで守られたオブジェクトなので、再生成されず、無限ループしない!

return (

カウント: {count}

);
}

`useMemo`を使うことで、Reactくんに「これはさっきと同じ付箋だよ、安心して!」と伝えることができ、無駄な再生成を防ぐことができるんです。

—

3. まとめ:今日から使える心構え

オブジェクトや配列の依存配列での挙動は、最初は本当に頭がこんがらがります。「なんで動かないの!?」とイライラしてしまうこともあるかもしれません。

でも、頭の片隅に次のルールを置いておくだけで、ぐっと楽になりますよ。

1. `useEffect`の依存配列に、コンポーネント内で普通に作ったオブジェクトや配列をそのまま置かない!
2. 監視したいなら、中身の「文字列」や「数値」など、バラバラの値にしてから渡してあげる。
3. どうしてもオブジェクトが必要なときは、`useMemo`で固定することを思い出してみる。

もしまた画面が固まったり、コンソールが真っ赤になったりしても、「あ、またReactくんが別人のそっくりさんだと勘違いしてるな」と優しく微笑んであげてくださいね。

あなたのReact開発が、少しでもスムーズで楽しいものになりますように。これからも一緒に一歩ずつ進んでいきましょう!

コメント

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