こんにちは!Reactの学習、楽しく進められていますか?
画面をポチポチ押したら動く!……というのは最高に楽しい瞬間ですが、少し複雑なアプリを作ろうとした途端、「あれ? なんでこの `useEffect`、無限ループしてるんだ……?」 と冷や汗をかいた経験、ありませんか?私は数え切れないほどあります(笑)。
特に、画面の状態(State)が変わるたびに別の処理を走らせる `useEffect` を書いていると、依存配列(`[]` の中に書く変数たち)の扱いに頭を悩ませがちです。
今回は、そんな「依存配列の沼」からあなたを鮮やかに救い出してくれる、現場のプロも愛用するテクニック「関数型更新(Functional Update)」について、身近な例えを交えながら優しく紐解いていきましょう。
大丈夫、ひとつずつ見ていけば絶対に理解できますからね!
—
1. まずは「お買い物のレジ」でイメージしてみよう
突然ですが、スーパーでお買い物をしている場面を想像してください。
いま、あなたのカゴの中には「りんご」が 3個 入っています。
ここで、新しくりんごを 1個 追加するミッションが発生しました。
- 素朴なやり方(通常の更新):
「今、カゴの中に3個入っているから……それに1を足して、次は4個にする!」
(※これだと、もし裏で誰かがこっそりカゴにりんごをもう1個入れていたら、計算が狂ってしまいますよね)
- 賢いやり方(関数型更新):
「レジの店員さん、『今、カゴの中にある個数』をそのまま教えて! そそこに1個プラスして新しい個数にするから!」
この「今どうなっているか」を直接見るのではなく、「最新の状態を教えてもらう仕組み」を使うのが、Reactにおける関数型更新の考え方なんです。
—
2. Reactでよくある「うっかりミス」を見てみよう
カウンターの数字を「1秒ごとに自動で1ずつ増やしていく(カウントアップ)」というコンポーネントを作るとしましょう。初学者が一番最初に書きたくなるコードは、大体こんな感じです。
import { useState, useEffect } from ‘react’;
function MuriNaCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
// カウントを1増やす
setCount(count + 1);
}, 1000);
// クリーンアップ処理(お片付け)
return () => clearInterval(timer);
}, [count]); // ← ここに count が入っている!
return (
現在のカウント: {count}
);
}
このコード、動くには動きます。動くのですが……ちょっと待ってください。
依存配列に `count` が入っていますよね。ということは、`count` が変わるたびに `useEffect` の中身(タイマー)が一度壊されて、また新しく作り直される という動きをしています。
今回はシンプルだからいいものの、もしこの中にAPI通信や重い処理が入っていたら、毎秒タイマーが破棄されて作り直されるのは、アプリにとってかなりの負担(パフォーマンス低下)になってしまいます。
かといって、依存配列から `count` をスポッと抜いて `[]` にすると、今度は `count` が永遠に `0` のまま増えなくなってしまう……。あっちを立てればこっちが立たず、まさにジレンマですね。
—
3. 救世主!「関数型更新」で依存の呪縛を解き放とう
ここで登場するのが、今回の主役である関数型更新です。
`setCount` の中に、直接数字を入れるのではなく、「古い状態を受け取って、新しい状態を返す関数」を渡してあげます。
実際のコードを見てみましょう。
import { useState, useEffect } from ‘react’;
function SmartCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
// setCountの中に「関数」を渡す!
// 引数の 「prevCount」には、直前の最新のカウントが入ってくるよ
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => clearInterval(timer);
}, []); // なんと、依存配列が空っぽ([])になりました!
return (
現在のカウント: {count}
タイマーは一度きりしか作られていません✨
);
}
どうでしょう? `setCount((prevCount) => prevCount + 1)` という書き方に変わっています。
この `prevCount`(名前は `current` でも `p` でも何でもOKです)には、Reactが知っている「いま現在メチャクチャ最新のStateの値」が自動的にポーンと渡されます。
だから、useEffectの依存配列に `count` を書く必要がなくなるんです。「あなたの今の値、外から見なくても自分で分かるから大丈夫だよ!」という状態ですね。
—
4. 関数型更新を使うと、どんないいことがあるの?
このテクニックが実務の現場で重宝される理由は、主に2つあります。
1. 依存配列から変数を減らせる(バグの温床を消せる)
余計な変数を依存配列に入れなくてよくなるため、「意図しないタイミングで `useEffect` が再実行されてしまう」というバグを根本から防げます。
2. パフォーマンスがスッキリ安定する
タイマーやイベントリスナーの登録・解除(セットアップとクリーンアップ)が無駄に繰り返されなくなるため、アプリの動作が軽やかになります。
特に、チャットアプリのメッセージ一覧に新しい発言を追加していく時や、ショッピングカートの数量変更ロジックなどでは、この関数型更新が命綱になることも少なくありません。
—
さいごに
Reactの `useEffect` や依存配列の仕組みは、最初はパズルを解いているように難しく感じられるかもしれません。「あれ、なんで動かないんだろ……」と手が止まる日だって、エンジニアなら誰でもあります。
でも、今回紹介した `setState(prev => …)` のように、「最新の状態を安全に受け取るテクニック」を一つずつ自分のものにしていくことで、コードはぐっとシンプルに、美しくなっていきます。
焦らず、自分のペースで少しずつ引き出しを増やしていきましょう。
あなたのReactライフを、心から応援しています!

コメント