優先度の低い更新を後回しにできるトランジション
React 19の「アクション」について触れる前に、React 18で導入された「トランジション」について簡単に復習しておきましょう。
「トランジション」とは「ステート更新を低優先度としてマーク」するための仕組みです。トランジションとしてマークしたステート更新は、レンダリングをブロックすることがなくなるため、特にレンダリング処理の重いコンポーネントにおいて、応答速度を改善できます。
例えば、大量のデータを取得してレンダリングする場合、データ取得後のレンダリング処理に時間がかかると、UIが一時的に固まってしまう可能性があります。
トランジションを利用すると、このような負荷の高いレンダリングを低優先度で処理でき、ユーザーにスムーズな操作体験を提供できるようになります。
React 18におけるトランジション
React 18のトランジションでは処理内容として同期関数しか指定できませんでした。リスト1は、React 18でuseTransitionを使ったトランジションの例です。
function TransitionExample() {
// useTransition を使ってトランジション中の状態を取得 ... (1)
const [isPending, startTransition] = useTransition();
// データ取得中の状態を管理 ... (2)
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState(null);
const loadData = () => {
setIsLoading(true);
fetchHeavyData() // 重いデータ取得処理
.then((result) => {
// データの更新をトランジション(低優先度)としてマーク ... (3)
startTransition(() => {
// 同期関数のみ指定可能 ... (4)
setData(result);
setIsLoading(false);
});
});
};
return (
<div>
<button onClick={loadData}>
{isPending || isLoading ? "読み込み中..." : "データ取得"}
</button>
{/* 描画の重いコンポーネント ... (5) */}
{data && <HeavyComponent data={data} />}
</div>
);
}
トランジション実行中の状態(isPending)をuseTransitionで取得できます。(1)が、fetchHeavyDataの実行中(3)はisPendingがtrueにはならないので、追加のisLoadingという別ステート(2)で非同期処理の状態を管理しています。
(3)では、startTransitionを使ってsetDataを低優先度で実行するように設定しています。そのおかげで、描画に時間のかかるHeavyComponentコンポーネント(6)のレンダリング中も、ユーザーは他の操作を実行できます。
ただし(4)にあるように、トランジションの中で実行できるのは同期関数のみであり、非同期処理を直接記述することはできません。
React 19における非同期トランジション
React 19では、トランジションが非同期関数を直接受け取れるようになりました。これによりリスト1で非同期処理の完了を追跡するためのisLoadingのような追加のステートが不要になります。トランジションが非同期関数の開始から完了までを管理してくれるからです。
リスト2は、リスト1をReact 19の非同期トランジションに書き換えた例です。
function TransitionExample() {
// isLoading は不要になった ... (1)
const [isPending, startTransition] = useTransition();
const [data, setData] = useState(null);
const loadData = () => {
// データ取得も含めた非同期処理をトランジションとして実行 ... (2)
startTransition(async () => {
const result = await fetchHeavyData();
setData(result);
});
};
return (
<div>
<button onClick={loadData}>
{isPending ? "読み込み中..." : "データ取得"}
</button>
{data && <HeavyComponent data={data} />}
</div>
);
}
ソースコードがぐっとシンプルになりました。(1)isLoadingが不要になり、ボタンの読み込み中表示の条件式も簡潔になっています。(2)startTransitionに直接非同期関数を渡せるため、データ取得もその中でawaitを使って行うことができ、then()を使用する必要がなくなりました。
