Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
優先度ベースの更新 — startTransitionによる緊急・非緊急レンダリング。
useTransitionはReact 18のHookで、状態更新を「非緊急トランジション」としてマークできます。緊急の更新(入力、クリック)は即座に処理され、中断されることはありません。トランジション更新は低優先度です — トランジション進行中に緊急の更新が到着すると、Reactはトランジションを中断し、緊急の作業を処理してからトランジションを再開始します。これにより、重い再レンダリング中もUIの応答性が維持されます。
Reactスケジューラーは各更新に優先度レーンを割り当てます:SyncLane(緊急、バッチ不可)、InputContinuousLane(ドラッグ/スクロール)、DefaultLane(通常のsetState)、TransitionLane(useTransition / startTransition)。高優先度のレーンは常に低優先度のレーンを先取りします。
startTransition(callback)内にsetStateの呼び出しをラップすると、Reactに「この更新はトランジションです — 中断可能です」と伝えます。ReactはTransitionLaneでスケジュールし、トランジションが完了するまで現在のUIを表示し続けます。
ユーザーが5000アイテムのリストがトランジションとして再レンダリング中に別の文字を入力すると、Reactはトランジションのレンダリングをツリーの途中で一時停止し、入力(緊急)を即座に処理し、入力を更新してから、新しい入力値でトランジションを最初から再開始します。
useTransitionは[isPending, startTransition]を返します。isPendingはReactがトランジションを処理している間はtrueです。UIをブロックせずに控えめなローディングインジケーターを表示するために使用します。
startTransitionの代替として:useDeferredValue(value)はトランジション中に遅れる値の遅延コピーを返します。UIは最初に古い遅延値でレンダリング(高速)し、Reactに時間ができたときに新しい値で再レンダリングします。
トランジション更新用の低優先度スケジューラーレーン。より高優先度の作業(ユーザー入力、同期更新)によって先取りされる可能性があります。
コールバック内のsetState呼び出しを非緊急としてマークします。スタンドアロンのインポートとして、またはuseTransitionフックから使用できます。
useTransitionからのブール値。Reactがまだトランジション更新を処理している間はtrue。入力をブロックせずにスピナーを表示するために使用します。
値の遅延コピーを返します。最初に古い値でレンダリング(即時)し、アイドル時に新しい値で再レンダリングします。大きなリストのフィルタリングに役立ちます。
トランジションの途中で緊急の更新が到着すると、Reactは進行中のトランジションレンダリングを破棄し、緊急の作業を処理してから新鮮な状態で再開始します。
1import { useState, useTransition } from 'react';23function SearchPage() {4 const [query, setQuery] = useState('');5 const [results, setResults] = useState(allItems);6 const [isPending, startTransition] = useTransition();78 function handleChange(e) {9 // Urgent: update the input immediately (never interrupted)10 setQuery(e.target.value);1112 // Non-urgent: filter 10,000 items as a transition13 startTransition(() => {14 setResults(allItems.filter(item =>15 item.name.includes(e.target.value)16 ));17 });18 }1920 return (21 <>22 <input value={query} onChange={handleChange} />23 {isPending && <Spinner />} {/* subtle loading indicator */}24 <ResultsList items={results} />25 </>26 );27}
トランジション以前は、Reactはすべての状態更新を同じ優先度でレンダリングしていました — 重いリストの再レンダリングが数百ミリ秒間入力をフリーズさせることがありました。トランジションにより、「ユーザーは入力を瞬時に感じる必要があるが、結果リストは少し時間がかかっても構わない」とReactに明示的に伝えられます。これが高コストなレンダリングを扱う際の、もたつくアプリと滑らかなアプリの違いです。
A dashboard has tabs: Overview, Analytics, Reports. Clicking 'Analytics' loads heavy chart components. Without transitions, the current tab disappears immediately and the user sees a loading blank until the new tab renders.
The tab switch calls `setActiveTab('analytics')`, which immediately unmounts the Overview tab and starts rendering Analytics. The heavy chart render blocks the main thread for 300ms. During this time, the user sees a blank panel — the old tab is gone, the new tab isn't ready.
Wrap the tab switch in `startTransition(() => setActiveTab('analytics'))`. React keeps showing the Overview tab (marked with `isPending` for a subtle opacity/spinner indicator) while rendering Analytics in the background. When ready, it swaps them in a single frame — no blank state.
Takeaway: useTransition is perfect for tab switching, accordion expansions, and any UI where you want the old content to stay visible while new content prepares. The `isPending` boolean lets you show a subtle loading indicator without destroying the current view.
A search results component receives `query` as a prop and does heavy filtering inline. You want to defer the results rendering but can't wrap the parent's setState in startTransition because you don't control the parent.
The parent component calls `setQuery(e.target.value)` directly. You can't add startTransition because you don't own the parent component (maybe it's from a library). Your heavy ResultsList re-renders on every keystroke because `query` prop changes immediately.
Use `useDeferredValue(query)` in ResultsList: `const deferredQuery = useDeferredValue(query)`. Filter using `deferredQuery` instead of `query`. React returns the old value during heavy renders, showing stale-but-fast results while computing the new filter in the background. Compare `query !== deferredQuery` to show a loading indicator.
Takeaway: useDeferredValue is the consumer-side equivalent of useTransition. Use useTransition when you control the state update (can wrap setState). Use useDeferredValue when you want to defer derived rendering from a prop you don't control.