核心概念

Suspense 是 React 中用于声明式处理异步操作和渐进式加载的并发特性。它允许组件在等待某些条件(如数据加载、代码分割)时”挂起”渲染,并显示一个降级的 UI,待条件满足后再继续渲染完整内容。

工作原理

基本机制

import { Suspense, lazy } from 'react';
 
// 动态导入 - 代码分割
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const DataComponent = lazy(() => import('./DataComponent'));
 
function App() {
  return (
    <div>
      {/* 立即渲染的静态内容 */}
      <header>应用头部</header>
      
      {/* 渐进式加载的异步内容 */}
      <Suspense fallback={<div>加载中...</div>}>
        <HeavyComponent />
      </Suspense>
      
      <Suspense fallback={<div>加载用户数据...</div>}>
        <DataComponent />
      </Suspense>
    </div>
  );
}

挂起与恢复流程

  1. 渲染开始:React 开始渲染组件树
  2. 遇到异步依赖:组件尝试读取尚未准备好的数据或代码
  3. 抛出 Promise:组件抛出 Promise 信号表示需要等待
  4. 挂起渲染:React 暂停当前组件的渲染
  5. 显示 Fallback:向上查找最近的 Suspense 边界,显示 fallback UI
  6. Promise 解决:异步操作完成
  7. 重新渲染:React 重新尝试渲染被挂起的组件
  8. 显示完整内容:渲染成功,替换 fallback UI

渐进式加载模式

1. 代码分割与懒加载

// 路由级别的渐进加载
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Settings = lazy(() => import('./pages/Settings'));
 
function Router() {
  const [currentRoute, setCurrentRoute] = useState('home');
  
  return (
    <Suspense fallback={<GlobalLoading />}>
      {currentRoute === 'home' && <Home />}
      {currentRoute === 'about' && <About />}
      {currentRoute === 'settings' && <Settings />}
    </Suspense>
  );
}
 
// 组件级别的渐进加载
function ProductPage({ productId }) {
  return (
    <div>
      {/* 核心信息立即显示 */}
      <ProductHeader productId={productId} />
      
      {/* 次要内容渐进加载 */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews productId={productId} />
      </Suspense>
      
      <Suspense fallback={<RecommendationsSkeleton />}>
        <RelatedProducts productId={productId} />
      </Suspense>
    </div>
  );
}

2. 数据获取的渐进式处理

// 简单的数据获取包装器
function fetchData(url) {
  let status = 'pending';
  let result;
  let suspender = fetch(url)
    .then(response => response.json())
    .then(data => {
      status = 'success';
      result = data;
    })
    .catch(error => {
      status = 'error';
      result = error;
    });
  
  return {
    read() {
      if (status === 'pending') {
        throw suspender; // 抛出 Promise,触发 Suspense
      } else if (status === 'error') {
        throw result; // 抛出错误,触发 Error Boundary
      } else if (status === 'success') {
        return result;
      }
    }
  };
}
 
// 在组件中使用
const userData = fetchData('/api/user/123');
 
function UserProfile() {
  const user = userData.read(); // 可能挂起
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}
 
// 在 Suspense 边界内使用
function App() {
  return (
    <Suspense fallback={<UserProfileSkeleton />}>
      <UserProfile />
    </Suspense>
  );
}

3. 嵌套 Suspense 与逐步揭示

function Dashboard() {
  return (
    <div className="dashboard">
      {/* 第一层:关键指标快速显示 */}
      <Suspense fallback={<KeyMetricsSkeleton />}>
        <KeyMetrics />
        
        {/* 第二层:详细数据稍后显示 */}
        <Suspense fallback={<ChartsSkeleton />}>
          <AnalyticsCharts />
          
          {/* 第三层:辅助信息最后显示 */}
          <Suspense fallback={<RecommendationsSkeleton />}>
            <Recommendations />
          </Suspense>
        </Suspense>
      </Suspense>
    </div>
  );
}

用户体验优化模式

1. 骨架屏设计

// 专业的骨架屏组件
function ProductCardSkeleton() {
  return (
    <div className="product-card-skeleton">
      <div className="image-placeholder"></div>
      <div className="content">
        <div className="title-line"></div>
        <div className="price-line"></div>
        <div className="button-placeholder"></div>
      </div>
    </div>
  );
}
 
function ReviewsSkeleton() {
  return (
    <div className="reviews-skeleton">
      <div className="review-header">
        <div className="avatar"></div>
        <div className="user-info">
          <div className="name-line"></div>
          <div className="date-line"></div>
        </div>
      </div>
      <div className="review-content">
        <div className="text-line"></div>
        <div className="text-line short"></div>
      </div>
    </div>
  );
}

2. 智能加载策略

import { Suspense, useState, useEffect } from 'react';
 
function SmartSuspense({ children, fallback, delay = 200 }) {
  const [showFallback, setShowFallback] = useState(false);
  
  useEffect(() => {
    // 延迟显示 fallback,避免快速加载时的闪烁
    const timer = setTimeout(() => {
      setShowFallback(true);
    }, delay);
    
    return () => clearTimeout(timer);
  }, [delay]);
  
  return (
    <Suspense fallback={showFallback ? fallback : null}>
      {children}
    </Suspense>
  );
}
 
// 使用智能 Suspense
function ProductPage() {
  return (
    <div>
      <SmartSuspense 
        fallback={<ProductGallerySkeleton />}
        delay={100} // 100ms 内加载完成就不显示骨架屏
      >
        <ProductGallery />
      </SmartSuspense>
      
      <SmartSuspense 
        fallback={<ProductDetailsSkeleton />}
        delay={300} // 次要内容可以容忍更长的延迟
      >
        <ProductDetails />
      </SmartSuspense>
    </div>
  );
}

错误处理与边界管理

import { Suspense } from 'react';
import ErrorBoundary from './ErrorBoundary';
 
function RobustAsyncComponent() {
  return (
    <ErrorBoundary 
      fallback={<ErrorComponent />}
      onRetry={() => window.location.reload()}
    >
      <Suspense fallback={<LoadingComponent />}>
        <AsyncContent />
      </Suspense>
    </ErrorBoundary>
  );
}
 
// 多个 Suspense 边界的独立错误处理
function ComplexPage() {
  return (
    <div>
      <ErrorBoundary fallback={<HeaderError />}>
        <Suspense fallback={<HeaderSkeleton />}>
          <PageHeader />
        </Suspense>
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<ContentError />}>
        <Suspense fallback={<ContentSkeleton />}>
          <PageContent />
        </Suspense>
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<SidebarError />}>
        <Suspense fallback={<SidebarSkeleton />}>
          <PageSidebar />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

性能优化实践

1. 预加载策略

// 预加载关键组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));
 
function App() {
  const [showHeavy, setShowHeavy] = useState(false);
  
  // 预加载组件
  useEffect(() => {
    import('./HeavyComponent');
  }, []);
  
  // 或者基于用户交互预测
  const handleHover = useCallback(() => {
    import('./HeavyComponent');
  }, []);
  
  return (
    <div>
      <button 
        onMouseEnter={handleHover}
        onClick={() => setShowHeavy(true)}
      >
        加载重型组件
      </button>
      
      {showHeavy && (
        <Suspense fallback={<div>快速加载中...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  );
}

2. 流式 SSR 集成

// Next.js 中的流式 Suspense
async function ProductPage({ params }) {
  return (
    <div>
      <ProductHeader productId={params.id} />
      
      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews productId={params.id} />
      </Suspense>
      
      <Suspense fallback={<RecommendationsSkeleton />}>
        <Recommendations productId={params.id} />
      </Suspense>
    </div>
  );
}

设计原则总结

1. 渐进式体验层次

  • 立即显示:静态内容和关键信息
  • 快速加载:用户当前视图内的内容
  • 延迟加载:非关键内容和预测性内容
  • 按需加载:用户交互触发的功能

2. 优雅降级策略

  • 骨架屏 → 提供结构和加载预期
  • 错误边界 → 防止级联失败
  • 重试机制 → 自动恢复能力
  • 离线降级 → 基础功能保障

3. 性能与体验平衡

  • 避免闪烁:智能延迟显示 fallback
  • 感知性能:骨架屏比加载动画更好
  • 优先级调度:关键内容优先加载
  • 预测加载:基于用户行为预加载

Suspense 将渐进式加载从技术实现提升到了声明式设计的层面,让开发者能够更自然地表达加载状态和依赖关系,为用户创造平滑、可预测的渐进式体验。