
今回紹介するのは、React初心者の私が実際に経験した「クラスとReactの挙動」に関する問題です。
機能実装の過程で計算基準を保持する計算クラスを作成しました。このクラスには計算メソッドを定義し、計算結果を返すようにしていましたが…
画面の計算基準の値は更新されているのに、計算メソッドで使われる計算基準は古いままという謎の現象に直面しました。なぜこんなことが起きたのか?この記事では、その原因・解決策を学びとして共有します。
起こった事象
◎実装の流れ
- クラス内で計算基準を private readonly で定義
- コンストラクタで計算基準を更新
- 計算基準を更新するコンポーネントと、結果を表示するコンポーネントが同階層のため、親で更新関数を定義してuseCallbackでメモ化
class Calculate {
private readonly itemA: number;
private readonly itemB: number;
private readonly itemC: number;
constructor(itemA: number, itemB: number, itemC: number) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
}
}

計算基準を変えるタイミングでstateに保持しているインスタンスも変わるので、関数の中でも自然と最新のものが使われるはずだと考えていたのですが…
実際に動かしてみると、画面の表示は更新されているのに、関数の中では古い計算基準のままという状態になってしまいました。
ではなぜ関数内で使われる値は更新されていなかったのか、原因を調べてみました。
原因
原因は、React が「値そのものの変化」ではなく「参照が変わったかどうか」で更新を判断する仕組みを、正しく理解できていなかったことでした。
◎useCallbackの依存配列に入っていても関数が更新されなかった理由
useCallback は、最初に作られた関数を保持し続け、依存配列に入っている値の「参照」が変わらない限り、関数を作り直しません。
今回のケースでは、
→クラスの内部の値だけ変えても、インスタンス自体の参照は同じのため、Reactからすると「変わっていない」と判断される
結果として、
useCallbackで更新されず、関数内ではずっと古いインスタンスが使われ続けてしまった、という流れです。
◎useState が効かなかった理由
ReactのuseState も、値の中身ではなく「参照が変わったかどうか」で更新を判断しています。
なので、クラスインスタンスの中身を書き換えても参照が変わらないため、Reactは「何も変わっていない」と判断して再レンダリングしません。
解決策
単純な話ですが、useStateをuseRefにすることで解決できました。
useRef は便利な存在で、
- useRef の参照自体は固定
- .current は自由に書き換え可能
- 最新の値を常に .current で取得できる
という特性を持っています。
この仕組みにより、
- useCallbackでも関数そのものは更新されない
- →関数の内部で ref.current を参照すれば、最新の計算基準をちゃんと使える
という形になり、今回の問題を解消できました。
まとめ
React のこうした細かい挙動は、最初はなんとなく「そういうもの」という理解で終わらせてしまいがちですが、ひとつひとつ深掘りしていくと「だからこう動くのか」と腑に落ちる瞬間に繋がります。
今回の件では、
◎React が「参照」によって変更を判断していること
◎useCallback は依存配列の参照が変わらない限り、最初にキャプチャした値を使い続けること
◎クラスインスタンスは内部が変わっても別物として扱われないこと、
こういった React の特性が絡み合って起こった問題でした。
今回このような問題に直面したことで、Reactの根本的な仕組みを理解するきっかけになったと思います。
これからも疑問をそのままにせず、理由を理解しながらより良いコードを書いていきたいと思います。
おわりに
KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp