<フックに関するよくある質問 – React> 前回の props や state はどうすれば取得できますか?を読み直していたときに挙動の理解に少し詰まったので、その解説です。
tl;dr
レンダーされたあとにその時点の値を保存しておくことで、次回のレンダリング時に「以前の値」を取り出せる。
前提理解
- React.useEffectはレンダリングされた後に実行される
- なので、このタイミングで「次回のレンダリング時にとっての前回の値」、つまり「現在の値」を格納しておく。
- 格納先はrefに格納する
- ref は何でも格納できる変数だと考えてOK
- 一般的にはDOMを紐付け・入れるケースが多いけど、実体はJavaScriptの生オブジェクトなので何を入れてもOK。
コードで見る
コードブロックごとに実行順番を記載しました。
1 -> 2 -> 3の順番で読み解いていくと理解しやすいかと思います。
import React from "react";
import ReactDOM from "react-dom";
const App: React.FC = () => {
/*************
* 1
*************/
const ref = React.useRef(null) as any;
const [count, setCount] = React.useState<number>(0);
const increment = () => setCount(prev => ++prev);
/*************
* 3
*************/
React.useEffect(() => {
console.log("[useEffect]count is ", count);
ref.current = count; // <= 現在の値を保存しておく。次回から見たら前回の値になる。
});
/*************
* 2
*************/
return (
<div className="App">
{console.log("[render]start---")}
{console.log("[render] prev is ", ref.current)}
{console.log("[render] count is ", count)}
<p>prev: {ref.current}</p>
<p>now: {count}</p>
<button onClick={increment}>+</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
console.logの結果は下記です。

ボタンをクリックするごとに赤枠ごとロギングされます。1つ目の赤枠は初回レンダリング時に表示されます。
おわりに
React.useEffectとrenderの実行順番を意識してなかったので、少し詰まったけどそこを意識すれば割と簡単に読めるかと思います。
ちなみに、usePreviousみたいにカスタムフックスにもできます。将来的にもReactから提供されるかもなので、極力カスタムフックスを作っておくほうが良いかと思います。(参照: フックに関するよくある質問 – React)