Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Coding Challenge Practice - Question 11

2025年09月20日に「Dev.to」が公開したITニュース「Coding Challenge Practice - Question 11」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Reactで前回の値を返すカスタムフック`usePrevious`の作成方法を解説。`useRef`で値を保持し、`useEffect`でレンダリング後に現在の値を保存することで、再レンダリングなしに常に前回の値を参照できる。初期値は`undefined`。

出典: Coding Challenge Practice - Question 11 | Dev.to公開日:

ITニュース解説

ニュース記事では、Reactアプリケーション開発で非常に便利なusePreviousというカスタムフックの作成方法が解説されている。このusePreviousフックは、ある値の「一つ前の値」を簡単に取得できるようにするものであり、Reactのコンポーネントが再レンダーされる際に、特定の値を追跡したい場合に非常に役立つ。システムエンジニアを目指す初心者にとって、Reactの基本的な仕組みや、フックという概念、そしてそれらを組み合わせて独自の便利な機能を作る方法を理解する良い機会となるだろう。

Reactのコンポーネントは、その内部の状態(state)や外部から渡されるプロパティ(props)が変更されると、画面表示を更新するために「再レンダー」される。再レンダーされるたびに、コンポーネント内で宣言された通常の変数は基本的に初期化されてしまう。しかし、usePreviousフックのように「前回の値」を記憶しておきたい場面は少なくない。例えば、あるデータの変更を検知したり、アニメーションの開始条件を制御したりする際に、現在の値と過去の値を比較する必要がある。ここで問題となるのは、再レンダーされても消えてしまわないように、値をどこかに永続的に保持する必要がある点だ。

この課題を解決するために、記事ではReactが提供する二つの強力なフック、useRefuseEffectを組み合わせる方法が紹介されている。これらを理解することが、usePreviousフックの核心を理解する鍵となる。

まず、useRefフックについて説明する。useRefは、コンポーネントのライフサイクルを通じて値を保持できる「箱」のようなものだと考えると良い。この箱は、コンポーネントが再レンダーされてもその中身がリセットされることはない。useRef{ current: initialValue }のようなオブジェクトを返し、そのcurrentプロパティを通じて値にアクセスしたり、更新したりする。最も重要な特徴は、ref.currentの値を更新してもコンポーネントが再レンダーされないという点である。これは、値の保持には便利だが、その値の変更が直接画面表示に影響しない場合に特に有効だ。記事のコードではconst ref = useRef<T>()のように使われており、Tは任意の型を表すジェネリクスだ。初期値が指定されていないため、ref.currentは最初はundefinedとなる。

次に、useEffectフックについて説明する。useEffectは、コンポーネントのレンダーが完了した「後」に特定の処理を実行するためのフックである。これは、データの取得、イベントリスナーの設定、または今回のように値を保持するといった、コンポーネントの表示とは直接関係ない「副作用(side effect)」を管理するために使われる。useEffectは第二引数に「依存配列」と呼ばれる配列を受け取る。この配列に指定された値が変更された場合にのみ、useEffectの内部の処理が再度実行される。記事のコードではuseEffect(() => { ref.current = value; }, [value])と記述されており、valueという値が変更された場合にのみ、useEffect内の処理が実行されることを意味している。

これらのuseRefuseEffectを組み合わせてusePreviousフックがどのように機能するか、順を追って見ていこう。

usePreviousフックの定義はexport function usePrevious<T>(value: T): T | undefinedとなっている。<T>は、このフックが任意のデータ型(数値、文字列、オブジェクトなど)に対応できるようにするためのジェネリクスである。引数valueは、現在追跡したい値を指し、戻り値のT | undefinedは、指定された型Tの値、または初期状態のundefinedを返すことを示している。

フックの内部では、まずconst ref = useRef<T>()で、前回の値を格納するためのrefが作成される。この時点ではref.currentundefinedである。

次に、useEffect(() => { ref.current = value; }, [value])という部分がある。このuseEffectが実行されるタイミングが非常に重要だ。 Reactのコンポーネントが再レンダーされる際、以下のステップで処理が進む。

  1. コンポーネント(例えばAppコンポーネント)がレンダーを開始する。
  2. Appコンポーネント内でusePrevious(count)のような形でusePreviousが呼び出される。
  3. usePreviousフックの中でreturn ref.current;が実行される。このとき、ref.currentには「前回のレンダー時にuseEffectによって保存された値」が格納されている、もしくは初回の場合はundefinedが格納されている。この値がprevCountとして返される。
  4. Appコンポーネントのレンダーが完了し、画面に表示される。
  5. Appコンポーネントのレンダーが完了した「後」に、usePreviousフック内のuseEffectが実行される(依存配列[value]、つまりcountの値が変更されている場合のみ)。
  6. useEffectの中でref.current = value;が実行され、現在のcountの値がref.currentに保存される。

このステップによって、usePreviousフックが呼び出されて値が返される時点では、ref.currentには「一つ前のレンダー時の値」が保持されており、フックからその値が返された後に、「現在の値」がref.currentに上書き保存される、という巧妙な仕組みが実現される。これにより、次にコンポーネントが再レンダーされる時には、現在のref.currentに一つ前の値が格納されている状態となるわけだ。

記事の最後に示されているカウンターアプリの例で、この動作を具体的に見てみよう。 Appコンポーネントでは、const [count, setCount] = useState(0)countという状態変数を管理している。初期値は0だ。 そして、const prevCount = usePrevious(count)で、現在のcountusePreviousフックに渡し、その一つ前の値を取得してprevCountに代入している。

初回のレンダー時:

  • count0である。
  • usePrevious(0)が呼ばれる。
  • refは初期状態なので、ref.currentundefined
  • usePreviousundefinedを返し、prevCountundefinedとなる。
  • 画面には「Now: 0, previous: undefined」と表示される。
  • 画面表示後、useEffectが実行され、ref.currentに現在のvalue(つまり0)が保存される。

「Increment」ボタンを一度クリックした場合:

  • setCount(c => c + 1)によりcount1になる。
  • Appコンポーネントが再レンダーされる。
  • usePrevious(1)が呼ばれる。
  • この時、ref.currentには前回のレンダー後に保存された0が格納されている。
  • usePrevious0を返し、prevCount0となる。
  • 画面には「Now: 1, previous: 0」と表示される。
  • 画面表示後、useEffectが実行され、ref.currentに現在のvalue(つまり1)が保存される。

もう一度「Increment」ボタンをクリックした場合:

  • setCount(c => c + 1)によりcount2になる。
  • Appコンポーネントが再レンダーされる。
  • usePrevious(2)が呼ばれる。
  • この時、ref.currentには前回のレンダー後に保存された1が格納されている。
  • usePrevious1を返し、prevCount1となる。
  • 画面には「Now: 2, previous: 1」と表示される。
  • 画面表示後、useEffectが実行され、ref.currentに現在のvalue(つまり2)が保存される。

このように、usePreviousフックはuseRefuseEffectを巧妙に組み合わせることで、再レンダーが発生しても永続的に値を保持し、その一つ前の値を取り出すことを可能にしている。システムエンジニアを目指す皆さんにとって、このようなカスタムフックの実装は、Reactの内部動作への理解を深め、より柔軟で再利用可能なコンポーネントを開発するための重要な一歩となるだろう。Reactで複雑なUIや状態管理を行う際に、このような「前回の値」を追跡する仕組みは非常に役立つため、ぜひこの概念と実装方法を理解しておくと良い。

関連コンテンツ