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.
使用 Suspense 边界实现降级渲染、代码分割和流式 SSR。
Suspense 是一种让组件在渲染前"等待"某些事情的机制。当组件尚未准备好(懒加载导入尚未加载,或数据获取尚未完成)时,它抛出一个 Promise。React 捕获这个抛出的 Promise,在树上找到最近的 Suspense 边界,显示其降级 UI,并在 Promise 解析时自动重新渲染子树。
React.lazy(() => import('./MyComponent')) 将动态导入包装在特殊包装器中。React 第一次尝试渲染它时,import() 还未解析,所以懒加载包装器抛出一个 Promise。
在渲染阶段,React 捕获抛出的 Promise(不是抛出的 Error — 那些去错误边界)。它向上遍历 Fiber 树找到最近的 <Suspense> 祖先,并渲染其 fallback prop 而不是暂停的子树。
React 向抛出的 Promise 附加 .then() 处理器。当 Promise 解析(模块加载,或数据到达)时,React 从头开始重新渲染暂停的子树,这次使用解析后的值。
在并发模式下,React 可以"尝试"渲染暂停的子树而不提交到 DOM。如果它暂停,React 继续显示旧内容(不闪烁到降级)直到 Promise 解析,然后原子性地换入新内容。
使用 Next.js/React 18 流式传输,服务器立即发送带有 Suspense 降级的 HTML shell。随着每个异步数据获取在服务器上完成,React 将填充后的 HTML 块流式传输到浏览器,内联替换降级内容。
包装动态导入以创建懒加载组件。第一次渲染时抛出 Promise。解析后,React 使用加载的模块重新渲染。
<Suspense fallback='{'<Spinner/>'}'> 捕获任何后代抛出的 Promise。等待时显示降级,准备好后显示子组件。
组件表示未准备好的机制。任何组件(不只是懒加载组件)都可以抛出 Promise 来触发 Suspense — 这就是 React Query 等数据获取库的集成方式。
服务器逐步发送 HTML。Shell 立即渲染带降级。随着异步工作完成,React 将填充的内容块流式传输到浏览器。
协调多个 Suspense 边界的显示顺序。可以强制 'together'(一次全部显示)或 'forwards'/'backwards'(按顺序显示)。
1// Code splitting with React.lazy2const HeavyChart = React.lazy(() => import('./HeavyChart'));34function App() {5 return (6 <Suspense fallback={<Spinner />}>7 <HeavyChart /> {/* loads the JS bundle on demand */}8 </Suspense>9 );10}1112// Data fetching (with a Suspense-compatible library)13// The library throws a Promise internally when data isn't ready14function UserProfile({ userId }) {15 // use() hook (React 19) or library wrapper throws if not ready16 const user = use(fetchUser(userId));17 return <h1>{user.name}</h1>;18}1920<Suspense fallback={<ProfileSkeleton />}>21 <UserProfile userId={1} />22</Suspense>
Suspense 将三个之前独立的问题 — 代码分割、加载状态和服务器流式传输 — 统一在一个声明式模型下。不再需要在每个组件中跟踪 isLoading 标志,你声明一个边界和降级,React 处理编排。这导致数据获取代码显著简化,并消除了加载状态管理 bug。
Your SPA ships a 2MB JavaScript bundle. The landing page only needs 200KB of it — the rest is for the admin panel, chart library, and markdown editor that 80% of users never visit.
Without code splitting, the browser downloads, parses, and executes 2MB of JavaScript before showing anything. On mobile (3G network + slower CPU), this means 8+ seconds before the page is interactive — most users bounce.
Use `React.lazy` + `Suspense` at route boundaries: `const AdminPanel = React.lazy(() => import('./AdminPanel'))`. The initial bundle drops to 200KB. When a user navigates to /admin, the AdminPanel chunk loads on demand. The Suspense fallback shows a skeleton while the chunk downloads.
Takeaway: Code splitting at route boundaries is the highest-impact optimization for initial load time. Wrap lazy-loaded routes in Suspense with skeleton fallbacks. Tools like Next.js do this automatically for page components, but you should also lazy-load heavy feature components within pages.
A dashboard page loads 3 things: sidebar navigation (fast), main content (medium), and analytics charts (slow). With a single Suspense boundary, the entire page shows a spinner until the slowest component (charts) finishes loading.
One Suspense boundary at the page level means the entire page is either 'loading' or 'ready'. The sidebar (50ms) and main content (200ms) are ready quickly, but the user stares at a full-page spinner for 2 seconds waiting for the charts.
Nest multiple Suspense boundaries: one around the sidebar (shows instantly), one around the main content (shows after 200ms), one around the charts (shows after 2s). Each section renders independently as its data/code becomes available. The page progressively reveals content instead of all-or-nothing.
Takeaway: Use nested Suspense boundaries to control loading granularity. The boundary placement determines the loading UX: coarse boundaries (page-level) show everything at once, fine boundaries (component-level) enable progressive, streaming-style loading.