人人都会AI编程

资源预加载、懒加载策略

更新时间:2026-07-11

页面加载性能直接决定了用户的第一印象。资源预加载和懒加载是两种互补的优化手段:懒加载推迟非关键资源的加载时机,减少首屏负担;预加载提前获取即将用到的资源,让后续交互瞬间完成。合理搭配使用,能让应用既快又流畅。

懒加载的核心手段

1. 路由级代码分割

在 React 中,React.lazySuspense 是最基础的懒加载工具。将页面组件通过动态 import() 引入,每个页面会被打包成独立的 chunk,只有访问时才加载。

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

2. 组件级按需加载

并非只有路由才需要懒加载。大型的弹窗、图表库、富文本编辑器等非首屏直接可见的组件,都可以用同样的方式拆分。

const HeavyChart = lazy(() => import('./components/HeavyChart'));

function ReportPage() {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowChart(true)}>查看图表</button>
      {showChart && (
        <Suspense fallback={<div>图表加载中...</div>}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
}

3. 图片懒加载

对于长列表或包含大量图片的页面,浏览器原生的 loading="lazy" 属性是最简单有效的方式。

function ImageGallery({ images }) {
  return (
    <div className="gallery">
      {images.map(img => (
        <img
          key={img.id}
          src={img.src}
          alt={img.alt}
          loading="lazy" // 浏览器原生懒加载
          width={img.width}
          height={img.height}
        />
      ))}
    </div>
  );
}

对于需要更精细控制(如占位符、加载动画)的场景,可使用 IntersectionObserver 实现自定义懒加载逻辑,或直接使用 react-lazyload 等成熟库。

预加载的实用策略

1. 鼠标悬停或意图预加载

当用户将鼠标悬停在某个链接或按钮上时,可以趁机预加载目标页面的代码,这样真正点击时页面几乎瞬间呈现。

function Navigation() {
  const preloadDashboard = () => {
    // 动态 import,Webpack/Vite 会自动分离 chunk
    import('./pages/Dashboard');
  };

  return (
    <nav>
      <Link to="/" onMouseEnter={preloadDashboard}>仪表盘</Link>
    </nav>
  );
}

这种方式不需要你手动操作 <link> 标签,构建工具会处理好一切。Vite 和 Webpack 都支持动态 import 的自动分割。

2. 关键资源的 Preload 和 Prefetch

  • <link rel="preload">:告诉浏览器“当前页面马上要用这个资源,最高优先级下载”。适合首屏必须的字体文件、关键 CSS 或主要图片。
  • <link rel="prefetch">:告诉浏览器“这个资源将来可能会用到,空闲时提前下载”。适合下一页面可能用到的 JS 或数据。

在 React 应用中,可以通过 React Helmet 或 Next.js 的 <Head> 组件动态插入这些标签:

import { Helmet } from 'react-helmet';

function ProductPage() {
  return (
    <>
      <Helmet>
        {/* 预加载首屏关键图片 */}
        <link rel="preload" href="/hero-banner.jpg" as="image" />
        {/* 预取下一步很可能访问的页面 */}
        <link rel="prefetch" href="/checkout.js" as="script" />
      </Helmet>
      {/* 页面内容 */}
    </>
  );
}

3. 数据预加载

除了代码和静态资源,数据也可以提前获取。TanStack Query 提供了 prefetchQuery 方法,可以在用户即将访问某个页面时预加载数据,减少等待时间。

import { useQueryClient } from '@tanstack/react-query';

function UserList() {
  const queryClient = useQueryClient();

  const prefetchUser = (userId) => {
    queryClient.prefetchQuery({
      queryKey: ['user', userId],
      queryFn: () => fetchUser(userId),
    });
  };

  return (
    <ul>
      {users.map(user => (
        <li
          key={user.id}
          onMouseEnter={() => prefetchUser(user.id)}
        >
          <Link to={`/user/${user.id}`}>{user.name}</Link>
        </li>
      ))}
    </ul>
  );
}

当用户悬停列表项时,用户详情的数据已经在后台加载,点击跳转后立刻就有缓存数据可用。

懒加载与预加载的配合原则

  • 首屏优先,其他推迟:首屏所需资源使用 preload 确保尽早到位,非首屏内容懒加载处理。
  • 预判用户行为:利用悬停、滚动到接近区域等信号触发预加载,时机在你判断“用户极大概率会点击”时。
  • 避免过度预加载:预加载会占用网络带宽和 CPU,如果用户最终没有使用,就是浪费。要有选择性地预加载,而不是一股脑全部 prefetch。
  • 利用工具自动分析:Next.js 等框架会自动为页面中的 <Link> 组件预取目标页面资源,无需手写任何预加载代码。纯 React 项目则需要根据路由库和环境自行配置。

实际开发中的常见问题与处理

1. React.lazy 不支持命名导出

如果你引入的模块使用了命名导出而非默认导出,需要用中间对象包裹:

// 错误:如果 OtherComponent 是命名导出
const OtherComponent = lazy(() => import('./OtherComponent'));

// 正确:转换一下
const OtherComponent = lazy(() =>
  import('./OtherComponent').then(module => ({ default: module.OtherComponent }))
);

2. 组件懒加载导致的闪动问题

Suspense 的 fallback 和实际组件渲染切换可能导致布局抖动。确保 fallback 与实际组件占据相同的空间,或者使用 useTransition 让旧内容保持显示直到新内容准备好。

3. 构建工具配置

在 Vite 中,动态 import 默认自动分割 chunk,无需额外配置。如果需要对 chunk 分组或命名,可以使用魔法注释:

const Dashboard = lazy(() => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard'));
// Vite 暂不支持 webpack 魔法注释,但可通过其他方式分组

Vite 的 chunk 分割策略基于 Rollup,可以在 vite.config.js 中通过 output.manualChunks 控制。


通过有意识的懒加载和预加载策略,你可以在保持应用功能完整的同时,将首屏加载时间缩短到用户可接受的 2-3 秒以内,后续交互也能保持极快的响应速度。这两个技术是前端性能优化中最容易落地且收益最明显的方案之一。