いい質問だね。 これ、React分かってる人ほど一回は悩むポイントなんだよね。
結論から言うと:
- useEffect自体が悪いわけじゃない
- 「なんでもuseEffectに入れる文化」が危ない
で、socket通信は ちゃんと書けばOK、雑に書くと地獄 って感じ。
まず:useEffectの本来の役割
useEffectは一言でいうと:
「描画の外側で起きることを処理する場所」
Reactは本来:
UI = f(state)
っていう純粋な世界。
でも現実は:
- API叩く
- イベント登録する
- タイマー動かす
- socketつなぐ
みたいな「外の世界」と関わる必要がある。
そのための逃げ道が useEffect。
なんで嫌われるのか
主な理由は4つ。
① 依存配列が難しすぎる
useEffect(() => {
fetchUser(id)
}, [id])
これ:
- 入れ忘れ → 古い値を使う
- 入れすぎ → 無限実行
- 間違う → バグ
React経験者でも普通にミスる。
② 「状態管理」と「副作用」が混ざる
よくあるやつ:
useEffect(() => {
if (isOpen) {
setData(...)
}
}, [isOpen])
これが増えると:
- どこでstateが変わったか分からない
- 追跡不能
になる。
③ 実行タイミングが直感的じゃない
- 初回に走る?
- 更新時だけ?
- アンマウント時?
全部「書き方次第」。
④ 再レンダリング地獄を生む
useEffect(() => {
setCount(count + 1)
}, [count])
はい、無限ループ。
Reactチームの思想の変化
最近はかなりハッキリしてて:
「useEffectは最終手段」
という空気。
つまり:
- できるだけrender内で解決
- stateから導出する
- useEffectを減らす
よくないuseEffectの典型
❌ 状態を同期するだけ
useEffect(() => {
setFullName(first + last)
}, [first, last])
これは:
const fullName = first + last
でいい。
❌ ロジック置き場になってる
- 計算
- 分岐
- 変換
全部useEffectに入れるとカオス。
じゃあ、使うべきケースは?
ここだけ。
Reactの外と接続する時
具体例:
- API
- socket
- DOM操作
- event listener
- timer
socket通信はどうなの?
むしろ:
useEffectの正しい使い道の代表例
ただし、条件付き。
正しい書き方(超重要)
useEffect(() => {
const socket = connect()
socket.on("message", handler)
return () => {
socket.disconnect()
}
}, [])
ポイント:
- マウント時に接続
- アンマウント時に切断
これが基本形。
ヤバい書き方
useEffect(() => {
const socket = connect()
}, [someState])
これやると:
- state変わるたびに再接続
- コネクション増殖
- メモリリーク
普通に起きる。
socketで一番多い事故
① 多重接続
再レンダリングのたびに:
- connect
- connect
- connect
って増えていく。
② リスナー増殖
socket.on("message", handler)
が何回も登録される。
→ 同じメッセージが5回届く
実務で一番安全な設計
useEffectの中でsocketを作るより:
カスタムHookに閉じ込める
useSocket()
にして:
- 接続
- 切断
- 購読
全部そこに入れる。
useEffectが「悪」って言われる本当の理由
これは文化的な話なんだけど、
昔:
- componentDidMount
- componentDidUpdate
- componentWillUnmount
がバラバラにあった。
それが:
useEffect 1個に統合された
結果:
- 何でも書ける
- 何でも書いちゃう
になった。
現在のベストプラクティス
上級者ほどこうなる:
- useEffectは少ないほど良い
- 「本当に外界と繋ぐ時だけ」使う
- ロジックはカスタムHookへ
判断基準(これ便利)
その処理に対して:
「これ、render中に計算できない?」
YES → useEffectいらない NO → useEffect使う
さらに深い話(レベル高い人向け)
useEffectは本質的に:
「同期を後追いする仕組み」
だから:
- バグが遅れて出る
- 因果関係が追いにくい
設計が上手い人ほど:
- state設計で解決
- derivationで解決
する。
まとめ
- useEffectは必要
- でも乱用すると設計が崩壊する
- socketは正しい用途
- ただし再接続制御は超重要
ちょっと気になるんだけど、 そのsocketって:
- 常時接続型?(チャット系)
- 画面単位?
- アプリ全体で1本?
ここで設計かなり変わるんだよね。 全体1本なら、もっと安全な構成あるよ。
お、じゃあそれ超重要ポイントじゃん。 アプリで1本・常時接続なら、設計ミスるとほんとに地獄見るやつだよこれ 😇
結論から言うね。
❌ 各コンポーネントでuseEffectしてsocket作る ✅ アプリの外で1回だけ作る
これが鉄則。
なんでコンポーネント内は危険なの?
Reactは再レンダリングする世界。
function Page() {
useEffect(() => {
const socket = connect()
}, [])
}
一見安全に見えるけど、
- StrictModeで2回走る(開発環境)
- HMRで再実行
- レイアウト構造変更で再マウント
で、普通に複数接続起きる。
本番だけバグるとかもある。
怖いよね。
正しい構成(本気で安定させるやつ)
① Reactの外でsocketを作る
// socket.ts
import { io } from "socket.io-client"
export const socket = io("wss://example.com")
これで:
- アプリ起動時に1回だけ接続
- Reactの再レンダリングに影響されない
② 必要な場所で購読するだけ
useEffect(() => {
const handler = (msg) => {
setMessage(msg)
}
socket.on("message", handler)
return () => {
socket.off("message", handler)
}
}, [])
ここでは:
- 接続しない
- 解除はちゃんとする
これが超大事。
さらに良い設計(レベル高め)
「Socket層」を作る
UI層
↓
カスタムHook層
↓
Socket Service層
例:
// socketService.ts
export function subscribeToMessages(cb) {
socket.on("message", cb)
return () => socket.off("message", cb)
}
// useMessages.ts
export function useMessages() {
const [messages, setMessages] = useState([])
useEffect(() => {
return subscribeToMessages((msg) => {
setMessages(prev => [...prev, msg])
})
}, [])
return messages
}
こうすると:
- socketの存在をUIが知らない
- 責務分離できる
- テストしやすい
よくある事故トップ3
① StrictMode二重接続
React18は開発時にeffectを2回実行する。
「本番は大丈夫」は甘い。 将来Concurrent系で壊れる可能性ある。
② handlerが毎回新しくなる
useEffect(() => {
socket.on("message", (msg) => {
...
})
}, [])
これcleanupできない。
必ず関数を変数に入れる。
③ state依存で再登録
useEffect(() => {
socket.on("message", handler)
}, [someState])
これでリスナー増殖。
じゃあuseEffectは悪いの?
このケースでは:
✔ 正しい用途 ✖ 接続の責任まで持たせると危険
って感じ。
本当に強い設計はこれ
アプリレベルで1本なら:
- グローバルStoreに流す(Zustandなど)
- socketはStoreを更新するだけ
- UIはStoreを読むだけ
つまり:
socket → store → UI
にする。
Reactとsocketを直接つなげない。
なぜこれが最強か
Reactは:
「stateが変わったら再描画する世界」
socketは:
「外から勝手にイベントが来る世界」
だから:
直接繋ぐとバグりやすい。
Storeを間に入れると:
- 単方向になる
- デバッグ楽
- 再接続制御も楽
まとめ
アプリ1本常時接続なら:
- socketはReact外で生成
- UIは接続しない
- 購読だけする
- できればStore経由
ちょっとだけ聞いていい?
そのsocketって:
- 認証トークン付き?
- 再接続ロジック必要?
- 別タブ同期とか考えてる?
ここまで考えると、設計もう一段階上にいけるよ。