サイトアイコン IT NEWS

ReactでuseRefを使った値の保持と更新の正しい方法

Reactで状態を扱う場面では、useStateuseEffectの他に、再描画を伴わずに値を保持したいケースがあります。そのようなときに使われるのが、useRefです。

今回は、あるシステムで見られた「constで定義したuseRefに対して値を代入してもエラーにならないのはなぜか?」という疑問を出発点に、useRefの正しい使い方と注意点を整理します。

constで定義した変数に代入?エラーにならない理由とは

以下のようなコードを見たとき、不思議に思った方もいるかもしれません。

const endDtExtendedDays = useRef(0);
endDtExtendedDays.current = coupon.end_dt_min_interval_days;

一見すると、constで定義された変数に代入しているように見えますが、実はこれは参照先のプロパティを書き換えているだけであり、endDtExtendedDays自体の参照は変更されていないため、エラーにはなりません。

つまり、useRefの戻り値は以下のような構造を持っています。

{
  current: 任意の値
}

このcurrentプロパティは自由に読み書きでき、再描画のきっかけとはならないのが特徴です。

useRefが有効な場面とは

useRefは、以下のような目的でよく使われます。

たとえば、ボタン連打による処理の重複を防止する際には、以下のような使い方が有効です。

const processing = useRef(false);

const handleClick = () => {
  if (processing.current) return;

  processing.current = true;
  // 処理を実行
  setTimeout(() => {
    processing.current = false;
  }, 1000);
};

useEffectと連携する際の注意点

useRefの値は変更しても再描画が発生しないため、useEffectなどの副作用フック内で値を使う場合には注意が必要です。
以下のような実装例がありました。

useEffect(() => {
  if (startDt) {
    const now = new Date();
    const daysDiff = Math.abs(differenceInDays(now, startDt));
    const min = daysDiff <= 30
      ? addDays(now, endDtExtendedDays.current)
      : startDt;
    setMinDate(min);
  }
}, [startDt]);

このコードでは、endDtExtendedDays.currentの変更は依存配列に含まれていないため、変更後も再評価されません。

解決策

再描画に反映させたい場合は、useStateと組み合わせて以下のようにすると確実です。

const [endDtExtendedDays, setEndDtExtendedDays] = useState(0);

useEffect(() => {
  if (startDt) {
    const now = new Date();
    const isWithinRange = Math.abs(differenceInDays(now, startDt)) <= 30;
    const min = isWithinRange
      ? addDays(now, endDtExtendedDays)
      : startDt;
    setMinDate(min);
  }
}, [startDt, endDtExtendedDays]);

useEffect(() => {
  const load = async () => {
    const data = await fetchCoupon(couponId);
    setEndDtExtendedDays(data.end_dt_min_interval_days);
  };
  load();
}, []);

このようにすると、endDtExtendedDaysが変わった時点で再評価が行われるようになります。

よくある誤解とアンチパターン

useRefを使えば便利だからといって、全ての状態をこれで管理するのはおすすめできません。
特に次のような場合は避けるべきです。

まとめ

ReactにおけるuseRefの特徴を理解すれば、再描画の無駄を避けつつ、状態を効率よく保持することができます。

  • constで定義しても、useRefのcurrentは変更可能
  • currentの変更は再描画されない
  • useEffectとの併用時は依存配列に注意
  • UIと連動する値にはuseStateを使うべき

今回のようなシステム内の不具合や疑問点は、同じようにReactを利用している多くのエンジニアにとっても身近な問題です。
Reactの特性を正しく理解しておくことで、より信頼性の高いフロントエンド実装が可能になります。

モバイルバージョンを終了