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.
React 18 如何使用时间切片和优先级通道保持 UI 响应性。
并发渲染是 React 18 同时准备多个 UI 版本并中断长时间运行的渲染以处理更紧急更新的能力。渲染不再是阻塞主线程的单一同步操作,React 现在可以在树的中途暂停渲染工作,让出给浏览器,处理紧急更新,然后恢复或丢弃暂停的工作。
React 的 Fiber 协调器将每个组件表示为一小单元的工作(一个 Fiber)。渲染阶段一次处理一个 Fiber,在每个单元后检查是否有更高优先级的工作到达。如果有,它可以暂停并稍后回来。
React 的调度器将渲染工作分割成小块。每块后,它将控制权交还给浏览器 — 允许输入事件、动画和绘制发生。这防止 UI 在长时间渲染期间冻结。
React 18 使用 lanes 模型按优先级对更新进行分类:SyncLane(用户输入,必须立即)、InputContinuousLane(持续手势)、DefaultLane(普通 state 更新)、TransitionLane(非紧急 UI 过渡)。
如果在 React 处理低优先级渲染时高优先级更新到达,React 暂停低优先级工作,完整处理高优先级更新(包括提交),然后恢复或重新开始低优先级工作。
因为渲染阶段是纯粹的(没有副作用),React 可以安全地丢弃部分完成的渲染并重新开始,而不会破坏应用状态。副作用只在 commit 阶段发生,它永远不会被中断。
将渲染工作分割成约 5ms 的块,在块之间让给浏览器。在昂贵渲染期间保持 UI 响应。
将更新从 SyncLane(最紧急)分类到 OffscreenLane(最不紧急)的位掩码系统。决定调度顺序。
React 的内部任务调度器。管理渲染任务的优先级队列,根据优先级和截止时间决定下一步做什么。
将 state 更新标记为非紧急过渡的 API。告诉 React 可以为更紧急的更新中断这项工作。
提供 startTransition 和 isPending 布尔值的 hook,isPending 表示过渡是否正在进行中。
1import { useState, useTransition } from 'react';23function SearchPage() {4 const [query, setQuery] = useState('');5 const [results, setResults] = useState([]);6 const [isPending, startTransition] = useTransition();78 function handleInput(e) {9 // Urgent: update the input immediately10 setQuery(e.target.value);1112 // Non-urgent: filtering 10,000 results can wait13 startTransition(() => {14 setResults(filterItems(e.target.value));15 });16 }1718 return (19 <>20 <input value={query} onChange={handleInput} />21 {isPending && <Spinner />}22 <ResultsList results={results} />23 </>24 );25}26// Typing feels instant (urgent update)27// Results update when React has time (transition)
在 React 18 之前,任何触发慢速渲染的 state 更新都会冻结整个 UI,直到渲染完成。并发渲染意味着即使 React 在后台计算复杂更新,输入框打字、滚动和按钮点击也保持响应。这是 Suspense、流式 SSR 和 React 服务端组件的基础。
A product catalog with 50,000 items needs instant search. Using debounce (300ms delay) feels sluggish. Without debounce, every keystroke triggers a heavy filter that blocks the main thread and makes typing lag.
Traditional approach: debounce → wait 300ms → filter → re-render. The user types 'laptop' and sees nothing for 300ms after each keystroke. Or without debounce: type 'l' → 50ms filter blocks thread → 'a' keystroke is delayed → laggy typing experience.
Use `startTransition` to mark the filter as non-urgent. React processes the input update immediately (typing stays responsive), then renders the filtered results when the main thread is free. If the user types another character before the filter completes, React ABANDONS the in-progress render and starts the new filter — no wasted work.
Takeaway: Concurrent rendering replaces debouncing for CPU-heavy UI updates. It provides a better UX (no artificial delay) with better performance (abandoned renders waste no DOM work). Use useTransition for filtering, sorting, and computing derived state.
Clicking a navigation link to a data-heavy dashboard page freezes the UI for 500ms while the new page renders 200+ chart components. The old page disappears, a blank screen shows, then the new page appears — jarring.
Without concurrent rendering, React must complete the entire render synchronously. Clicking 'Dashboard' unmounts the current page and starts rendering 200 charts. The main thread is blocked for 500ms — no animations, no response to user input, just a white flash.
Wrap the navigation in `startTransition(() => navigate('/dashboard'))`. React keeps the old page visible and interactive while rendering the dashboard in the background. If the user clicks 'Back' during this time, React abandons the dashboard render. The transition completes smoothly with no blank screen.
Takeaway: Concurrent rendering enables 'optimistic UI' for navigation — the old page stays visible until the new one is fully ready. This eliminates white-flash navigation transitions and lets users cancel slow navigations by clicking elsewhere. Next.js App Router uses this pattern by default.