React Hooksの裏側 – なぜuseStateは非同期的に動くのか?

React HooksはReact開発を効率化し、関数コンポーネントでも状態管理やライフサイクル処理を容易にするために導入されました。その中でも特にuseStateはよく使われる基本的なフックですが、状態更新が即座に反映されず非同期に見えることがあります。本記事では、この挙動の原因を詳しく解説し、React開発でuseStateをより効果的に使う方法を明らかにします。

React Hooksの基本知識

React Hooksとは?

React HooksはReact16.8で導入された機能で、クラスコンポーネントを使わずに関数コンポーネント内で状態管理(useState)や副作用(useEffect)などを簡単に扱えるようにするものです。これによりコードがシンプルになり、可読性やメンテナンス性が向上します。

useStateの基本的な使い方(コード例)

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>現在のカウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>カウントアップ</button>
    </div>
  );
}

useStateが非同期に見える原因

useStateが非同期的だと誤解される具体例

useStateで状態を更新した直後に値を確認すると、期待する値ではなく、古い値が表示される場合があります。

const [count, setCount] = useState(0);

const handleClick = () => {
  setCount(count + 1);
  console.log(count); // 古い値が出力される
};

なぜ即座に更新されないのか?

useState自体は実際に非同期処理を行っているわけではなく、Reactのレンダリングサイクルにおいて、複数の状態更新を「バッチ処理」するためです。

Reactのレンダリングとバッチ処理の仕組み

Reactのレンダリングプロセスとは?

Reactは状態が変化するたびにコンポーネントを即座に再描画するのではなく、複数の更新をまとめて次回レンダリング時に一括処理します。

バッチ処理(Batching)とは何か?

バッチ処理とは、複数の状態変更を1回のレンダリングプロセスで処理することにより、パフォーマンスを最適化するReactの仕組みです。

バッチ処理がuseStateに与える影響

この仕組みの影響で、useStateが即座に更新結果を反映しないように見えることがあります。

React 18における自動バッチ処理の導入と影響

React 18の自動バッチ処理とは

React 18では、自動バッチ処理が拡張され、イベントハンドラだけでなく、Promiseやasync関数などでも自動的にバッチ処理が適用されます。

React 17以前との違い

従来はイベントハンドラ内でのみバッチ処理が有効でしたが、React 18以降ではPromiseやasync処理内でも動作し、レンダリング効率が大幅に改善されました。

自動バッチ処理が開発にもたらすメリットと注意点

レンダリングが最適化される反面、即座に状態を確認したい場合は、useEffectなどを用いた別の対策が必要になります。

useStateを適切に扱うためのベストプラクティス

useEffectを使った値更新の待機方法(コード例)

useEffect(() => {
  console.log(count); // 正しく更新後の値を確認できる
}, [count]);

useRefを使って状態を同期的に管理する方法(コード例)

const countRef = useRef(count);
useEffect(() => {
  countRef.current = count;
}, [count]);

const handleClick = () => {
  setCount(count + 1);
  console.log(countRef.current); // 最新の値を即座に参照可能
};

関数型アップデートを利用したテクニック(コード例)

const handleClick = () => {
  setCount(prevCount => {
    const newCount = prevCount + 1;
    console.log(newCount); // 正しく最新の値を表示
    return newCount;
  });
};

開発現場でよくあるuseStateのアンチパターンとその対策

陥りがちなアンチパターンの例

  • 状態更新直後の値を即時参照する
  • 無駄な再レンダリングを引き起こす状態管理

問題を防ぐためのポイントと推奨パターン

  • 状態変更を監視するためにuseEffectを使用する
  • 状態更新時に必ず関数型アップデートを活用する

まとめ

useStateが非同期的に見えるのは、Reactが複数の状態変更をバッチ処理しているためです。React 18の自動バッチ処理導入後は更に注意が必要ですが、useEffectやuseRef、関数型アップデートを活用すれば、効率的かつ安全に状態管理が行えます。

FAQ(よくある質問)

なぜuseStateは即座に反映されないのですか?

Reactがパフォーマンス向上のために行うバッチ処理が原因で、即座に更新結果が反映されないことがあります。

useStateの更新を即座に確認する方法はありますか?

useEffectを使った監視や、useRefを用いて値を保持する方法が有効です。

参考資料