1. はじめに
React 18 で導入された Concurrent Rendering(並列レンダリング) は、ユーザーの体験を損なわないスムーズなUI更新を実現しました。その中でも、useTransition
は 重い状態更新を後回しにしてUIをなめらかに保つ ための重要なフックです。
さらに、React 19 では「アクション (Actions)」という概念が導入され、useTransition
が非同期関数に対応することで、更新系の非同期処理の扱いが劇的に進化しました。
この記事では、useTransition
の基本的な使い方から、React 19での最新の非同期対応とベストプラクティスまでを実例付きで解説します。
2. 🎯 なぜ useTransition
が必要なのか?
Reactアプリでは、重い状態更新 が発生すると、メインスレッドがブロックされ、入力フォームやボタン操作が一時的に固まる現象(UIフリーズ)が起こり、ユーザー体験(UX)が悪化します。
例:
- フィルタリング時に大量のリストを再描画する
- SPAでページ遷移中に大規模なコンポーネントツリーが描画される
- 検索入力時に重いレンダリングが走る
こうしたケースで useTransition
を使うと、「ユーザー操作(入力など)を優先」「重い更新は後回し(低優先度)」 という優先度制御が可能になり、UIがなめらかに動作します。
シナリオ | 問題 |
useTransition の効果 |
---|---|---|
データフィルタリング | 入力中に重いリスト更新が走り、入力ラグ | 入力は即時反映、リスト更新は後回し |
フォーム/API送信 | 送信中のローディング状態管理とUIフリーズ | ボタン応答性を維持し、ローディングを自動管理 |
ページ遷移 | SPA内ルーティングで大規模描画によるちらつき | UIの応答性を保ちつつ、遷移をスムーズに |
3. 🧩 基本構文
useTransition
は常に2つの要素を含む配列を返します。
const [isPending, startTransition] = useTransition();
startTransition(() => {
// ここに書かれた setState は「低優先度」として処理される
setState(...);
});
変数名 | 役割 |
---|---|
isPending |
トランジション(低優先度の更新)が現在保留中かどうかを示す boolean 。ローディング表示などに使用します。 |
startTransition |
優先度の低い更新を包むための関数。この関数内のステート更新は低優先度で非同期的に処理されます。 |
🧠 従来の動作(React 18)
React 18 では、startTransition
に渡すコールバック関数は同期関数である必要がありました。
const handleClick = () => {
// このステート更新のみがトランジション化される
startTransition(() => {
setCount(count => count + 1);
});
};
4. 🚀 React 19 の「アクション (Actions)」と非同期対応
React 19 の主要なアップデートは「アクション (Actions)」の概念です。アクションは、更新系(データ変更、フォーム送信など)の非同期処理を行う関数のことを指します。
React 19 の新機能として、startTransition
に渡すコールバック関数を 非同期関数 (Promise を返す関数) にすることができるようになりました。
非同期アクションの組み込み例
フォーム送信やAPIコールなど、重い非同期処理を useTransition
でラップすることで、APIリクエスト完了までのローディング状態を自動的に管理し、ボタン操作の応答性を維持します。
function SubmitButton({ submitAction }) {
// isPending でローディング状態を自動管理
const [isPending, startTransition] = useTransition();
return (
<button
disabled={isPending}
onClick={() => {
// 非同期関数を startTransition でラップ
startTransition(async () => {
// submitAction は POSTリクエストなどの非同期関数を想定
await submitAction();
// (API結果に基づいたsetState更新もここで行える)
});
}}
>
{isPending ? 'Submitting...' : 'Submit'}
</button>
);
}
エラー処理について
非同期関数が失敗した場合、useTransition
でラップされたアクションから発生したエラーは、レンダリング中のエラーと同様にError Boundaryでキャッチされます。
ただし、更新系のアクションでは画面全体をエラー状態にするのは望ましくない場合が多いため、以下のようにアクション内でエラーを処理するのが一般的です。
startTransition(async () => {
try {
await submitAction();
} catch (error) {
// 画面遷移を伴わず、ユーザーにエラーメッセージを表示(トーストなど)
console.error("アクション失敗:", error);
// エラー状態を示すローカルなステートを更新することもできます
}
});
✅ ベストプラクティス:アクション関数の設計
React の useTransition
を使う場合、ボタンやフォームなどから呼び出す関数は 「アクション関数」として props で渡す ことが多いです。
const MyButton = ({ submitAction }) => {
const [isPending, startTransition] = useTransition();
return (
<button
disabled={isPending}
onClick={() => {
startTransition(async () => {
// 同期関数でも非同期関数でも await で対応可能
await submitAction();
});
}}
>
{isPending ? 'Submitting...' : 'Submit'}
</button>
);
};
🔹 ポイント
-
アクション関数は
xxxAction
と命名
例:submitAction
,deleteAction
,saveAction
「この関数はボタンやイベントのトリガー用アクション」という意図が明確になります。 -
同期・非同期関数どちらでも対応
await submitAction()
と書くことで、渡された関数が Promise を返す場合も返さない場合も安全に実行可能です。
useTransition
内では非同期処理を待つことでisPending
の状態管理が正しく機能します。 -
UI 応答性を維持
startTransition
で低優先度扱いにすることで、重い処理中でもボタンやフォームの操作がスムーズになります。
5. 🔄 useDeferredValue
との違い
useTransition
とともに Concurrent Rendering を支えるフックに useDeferredValue
があります。
フック | 役割 | 主な使い方 |
---|---|---|
useTransition |
**更新処理(setState)**を低優先度にする | 関数をラップして優先度を制御 |
useDeferredValue |
値の反映を遅延させる | 重いコンポーネントに渡す props /state の伝播を遅らせたい時 |
🔑
useTransition
は「いつ更新を始めるか」を制御し、useDeferredValue
は「更新された値をいつ表示するか」を制御します。これらを組み合わせることで、複雑なUX要求にも応えられます。
6. 🧭 まとめ
-
useTransition
は 「遅くする」のではなく「スムーズにする」 - 重い処理を後回しにして、UI応答性を維持するためのフックです。
- React 19 の アクション対応 により、非同期関数を直接渡せるようになり、ローディング管理が大幅に簡素化されました。
ReactのUXを劇的に改善するこれらの機能を活用しましょう。