useEffectは「最後の手段」である:Reactアーキテクトが語る副作用の最適化戦略
Reactのコードベースをレビューしていると、一目で「あ、このコンポーネントは重くなるな」と直感する瞬間がある。その典型が、「レンダリング中に計算可能な値を、わざわざuseEffect経由でstateに同期させているコード」だ。
我々が書くReactのコンポーネントは、本質的に「状態(State)からUI(View)への純粋な関数」であるべきだ。にもかかわらず、そこに `useEffect` を持ち込んでデータの同期を行おうとすると、Reactのレンダリングサイクルを二重に回すことになり、パフォーマンスと予測可能性の両面で負債を積み上げることになる。
今日は、その「useEffect依存症」を断ち切り、より堅牢でメモリ効率の高いアーキテクチャへ昇華させるための話をしよう。
—
なぜ「useEffectでの同期」がアンチパターンなのか
多くのエンジニアが犯す過ちは、`props` や `state` が変化したときに、別の `state` を更新するために `useEffect` を使ってしまうことだ。例えば、ユーザーの姓名からフルネームを生成するようなケースだ。
// 【アンチパターン】レンダリングの二度手間
const [firstName, setFirstName] = useState(‘John’);
const [lastName, setLastName] = useState(‘Doe’);
const [fullName, setFullName] = useState(”);
useEffect(() => {
// これが走るたびにコンポーネントは再レンダリングをトリガーする
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
このコードの何が問題か?
1. 二重レンダリング: 最初のレンダリングの後、`useEffect` が実行され、`setFullName` で再びレンダリングが走る。ブラウザは「見えない中間の状態」を計算させられ、貴重なCPUサイクルを浪費する。
2. 同期の不整合: 非同期処理が絡んだ場合、競合状態(Race Condition)が発生しやすく、UIと内部データの整合性が崩れる。
3. 複雑な依存配列: 依存配列が長くなればなるほど、デバッグは困難を極める。
—
「計算可能なら、その場で計算せよ」
答えは極めてシンプルだ。「派生状態(Derived State)は、レンダリング中に直接計算する」。これがReactの流儀だ。
// 【リファクタリング】純粋で高速なアプローチ
const [firstName, setFirstName] = useState(‘John’);
const [lastName, setLastName] = useState(‘Doe’);
// レンダリング中に計算する(コストが高い場合のみuseMemoを使う)
const fullName = `${firstName} ${lastName}`;
これだけでいい。`fullName` は常に最新の状態を保ち、不要な副作用の管理から解放される。もし計算コストが無視できないほど巨大な関数(例:数千件のデータフィルタリング)であれば、`useMemo` を使えばよい。
const filteredData = useMemo(() => {
// 高負荷な計算はメモ化してキャッシュする
return heavyCalculation(data, filterCriteria);
}, [data, filterCriteria]);
—
副作用の本当の役割を見極める
では、`useEffect` はいつ使うべきか?それは「Reactの管理外の世界」と同期するときだけだ。
- DOMの直接操作(Focusの制御、外部ライブラリの初期化)
- ネットワークリクエストのトリガー
- タイマーやイベントリスナーの登録
これら以外で `useEffect` を使っているなら、それは設計を見直すサインだ。特に「プロップスが変わったから特定のステートをリセットする」といったロジックを `useEffect` で書いているなら、それはキー(`key`)属性を活用してコンポーネント自体を再マウントする方が、遥かにクリーンでバグが少ない。
// 【キーを活用したリセット】
// ユーザーIDが変わるたびにコンポーネントを完全に初期化する
—
アーキテクトからの助言:思考を「宣言的」に変える
高度なフロントエンド開発において、最もコストが高いのは「バグの修正」ではなく「システムの複雑性の維持」だ。`useEffect` で副作用を連鎖させればさせるほど、システムは命令的(Imperative)になり、挙動が予測不可能になる。
- データフローを可視化せよ: 状態がどこから来て、どこで加工されるのかを、レンダリングサイクルという一本の直線上に並べること。
- 「同期」ではなく「派生」を考える: ステートを増やすのではなく、既存のステートから必要な情報を「切り出す」思考にシフトすること。
`useEffect` を減らすことは、単なる最適化ではない。それは、Reactという強力な抽象化の恩恵を最大限に引き出し、ブラウザのメインスレッドを解放するための、プロフェッショナルとしての誇りだ。
次に `useEffect` を書こうとしたとき、自問してほしい。「これは本当に、Reactのレンダリングサイクル外で行うべきことか?」と。その問いこそが、君のアプリケーションを次のステージへ押し上げる鍵になるはずだ。

コメント