useEffect の依存配列について

`useEffect` の依存配列は、「いつ useEffect の中の処理を実行するか」を React に伝えるための重要な仕組みです。

### 基本構文

```tsx
useEffect(() => {
  // 副作用の処理（例：API 呼び出し、イベント）
}, [依存変数1, 依存変数2, ...]);
```


### 依存配列の意味

| 依存配列の書き方     | 実行されるタイミング                           |
| ------------ | ------------------------------------ |
| `[]`（空配列）    | 初回マウント時のみ。1回だけ実行される。                 |
| `[依存変数1, 依存変数2]` | `依存変数1` または `依存変数2` が **変化したとき** に実行される。 |
| 書かない（省略）     | 毎回レンダリング後に実行される。**非推奨**。             |


## 依存配列が必要な場面

### ① API呼び出しなどの非同期処理

#### 例： 特定のパラメータが変わったときに API を再取得したい。

```tsx
useEffect(() => {
  fetch(`/api/data?query=${query}`).then(...);
}, [query]);  // query が変わるたびに再実行
```

#### ポイント

* `query` が変わったときだけリクエストしたい。
* 依存配列がないと、毎回レンダリングでリクエストしてしまいパフォーマンス悪化。


### ② イベントリスナーの登録・解除

####  例： ある条件でスクロールイベントを登録したい。

```tsx
useEffect(() => {
  const onScroll = () => console.log("scrolled!");
  window.addEventListener("scroll", onScroll);
  return () => window.removeEventListener("scroll", onScroll);
}, [enabled]);  // enabled の変化に応じてイベントを切り替え
```

#### ポイント

* 状態に応じてイベント登録を切り替える必要がある。
* 依存配列がないと、意図しないタイミングでイベントが追加・削除されない。


### ③ setInterval / setTimeout の使用

```tsx
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count);
  }, 1000);
  return () => clearInterval(timer);
}, [count]);
```

#### ポイント

* `count` が変わるたびに、**新しいクロージャ**に基づいて動作してほしい。
* 依存配列がなければ、古い `count` をずっと使ってしまう。


### ④ 親から渡された props の変化を監視したいとき

```tsx
useEffect(() => {
  console.log("親からの user が変わった", user);
}, [user]);
```

#### ポイント

* props の変化をトリガーに何かをしたい（ログを出す、状態を更新する、など）。

## なぜ依存配列が重要か

#### 1. **`useEffect` は副作用の実行タイミングを制御する仕組み**

React のコンポーネントは**何度も再レンダリング**されますが、副作用（例：API通信、DOM操作、イベント登録）はそのたびに無条件で行うと無駄やバグの原因になります。

そのため、 **必要なときだけ副作用を実行する**必要があり、その条件を指定するのが「依存配列」です。

#### 2. **依存配列 = 「この値が変わったら再実行していいよ」リスト**

React は依存配列を監視して、

* 変化があれば `useEffect` を再実行
* 変化がなければスキップ

という最適化を行います。


### 依存配列を省略するとどうなるか
```tsx
useEffect(() => {
  // 毎回実行される
  doSomething();
});
```

### デメリット 1：**パフォーマンスの悪化**

* レンダリングごとに副作用を実行してしまい、**不要な処理が増える**
* たとえば API リクエストを毎回送る → 無駄な通信・サーバー負荷増


### デメリット 2：**状態のループ・無限再実行のリスク**

```tsx
useEffect(() => {
  setValue("abc");
});
```

* `setValue` → 再レンダリング → `useEffect` → また `setValue`...
* **無限ループ**が発生する可能性あり（特に依存関係を持つ場合）

### デメリット 3：**古い値のまま処理される**

```tsx
useEffect(() => {
  console.log(count);  // 最新の count を見てない可能性がある
}, []);  // count に依存しているのに空配列にしてしまった
```

* 本来 `count` が変わるたびに実行されるべき処理が、
* 空配列により「初回しか動かない」＝**状態が変わっても処理されない**
---

## 難しい論点と設計ポイント


### 1. **関数・オブジェクト・配列の参照変化問題**

#### 問題
依存配列に関数やオブジェクト・配列など「**毎回新しく生成される値**」を入れると、**毎回再実行される**。

```tsx
useEffect(() => {
  doSomething();
}, [someFunc]); // someFunc は useEffect 外で毎回新しく定義されてると NG
```

#### 解決

* `useCallback` / `useMemo` でメモ化して、参照が変わらないようにする。

```tsx
const stableFunc = useCallback(() => {
  ...
}, [deps]);

useEffect(() => {
  stableFunc();
}, [stableFunc]);
```

* React は**浅い比較**しかしないので、再実行を避けるには**同一参照を保つ工夫**が必要。


### 2. **useEffect のクロージャトラップ**

#### 問題

依存配列に入れていない変数の古い値を `useEffect` がキャプチャして使ってしまう。

```tsx
function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // この中の count は「この useEffect が定義された時点の count」
    console.log(count);
  }, []);
}
```
上記の `useEffect` の中の `count` は、初回の `count=0` のときのスナップショットを「クロージャとしてキャプチャ」しています。

つまり、**`useEffect` の中だからといって、自動的に最新の値が使われるとは限りません。**

#### 解決

* **最新の値を使いたい場合は `ref` を使う。**

```tsx
const countRef = useRef(count);

// count が変わるたびに ref を更新
useEffect(() => {
  countRef.current = count;
}, [count]);

useEffect(() => {
  const id = setInterval(() => {
    console.log(countRef.current);  // 毎回最新の count
  }, 1000);
  return () => clearInterval(id);
}, []);  // setInterval 自体は1回だけでOK
```

### 3. **副作用のキャンセルとクリーンアップ**

#### シーン

非同期通信やタイマーを使っていて、**コンポーネントのアンマウント時や再実行時に前の処理を無効化したい**。

```tsx
useEffect(() => {
  let isMounted = true;
  fetch("/api").then((res) => {
    if (isMounted) setData(res);
  });
  return () => {
    isMounted = false;
  };
}, []);
```

#### または AbortController を使う

```tsx
useEffect(() => {
  const controller = new AbortController();
  fetch("/api", { signal: controller.signal }).catch((e) => {
    if (e.name !== "AbortError") throw e;
  });
  return () => controller.abort();
}, []);
```

### 4. **再実行を意図的に抑制・強制したいケース**

#### 条件によって実行したりしなかったりするような制御も可能

```tsx
useEffect(() => {
  if (!shouldRun) return;
  doSomething();
}, [shouldRun, triggerKey]);
```

→ 外部の `triggerKey` などをトグルして意図的に再実行させる設計。

---


## useEffect の依存配列まとめ

* `useEffect` は副作用を定義するフックで、**いつ実行するか**を依存配列で制御する。
* 依存配列に値を入れると、**その値が変わったときだけ実行**される。
* **省略すると毎回実行される**ため、パフォーマンス悪化や無限ループの原因になる。
* オブジェクト・配列・関数は参照が毎回変わるので、**`useMemo` や `useCallback` でメモ化**して安定させる。
* 最新の状態を非同期内などで参照したい場合は、**`useRef` で最新値を保持**するのが安全。